首页 > 解决方案 > 初始渲染时 useState 反应钩子的意外行为

问题描述

我正在构建一个简单的应用程序,该应用程序通过搜索的输入值使用 d3 图显示数据。

export const Search = () => {
  const [country, setCountry] = React.useState('');
  const [weatherData, setWeatherData] = React.useState([]);

  const changeText = e => {
    setCountry(e.target.value)
  }

  const searchCountry = () => {
    fetch(`https://apiweatherhistory/dayone/country/${country}`).then(res => res.json().then(data => {
      setWeatherData(weatherData)
    }))
  }

  const svg = d3.select('.weatherSearch')
    .append('svg')
    .attr('width', 960)
    .attr('height', 500)
    .style('background', 'black')

  return (
    <div>
      <input type='text' onChange={(e) => changeText(e)} value={country} />
      <button onClick={searchCountry}>Search</button>
      <div className='weatherSearch' />
    </div>
  )
}

安装组件后,d3 会在 div 标签名称weatherSearch上附加 svg 元素。

问题是组件渲染了两次,然后 svg 标记也被绘制了两次。但是当我注释掉两个状态初始化时,

  const [country, setCountry] = React.useState('');
  const [weatherData, setWeatherData] = React.useState([]);

该组件仅呈现一次并绘制一次 svg 标签。

输入或点击交互尚未发生,这意味着甚至没有使用两种状态更改方法(setCountry、setWeatherData)。

并且 Search 组件是 index.js 的根组件,因此父组件无法触发重新渲染。

为什么会这样?只有初始化状态才能触发重新渲染?

标签: javascriptreactjs

解决方案


我怀疑这是因为附加 svg 在功能组件主体中,因此每次“渲染”组件时都会执行它。我所说的“渲染”是指,当 React 框架在“渲染”阶段渲染 virtualDOM 树以计算差异时。这与计算 DOM 刷新到实际DOM 以及效果和其他生命周期函数运行时的“提交”阶段不同。“渲染”阶段可以通过几乎任意次数的反应来暂停、中止和重新启动。

在此处输入图像描述

我会将这个逻辑放在一个useEffect带有空依赖数组的钩子中,以便它在组件安装上运行。

export const Search = () => {
  const [country, setCountry] = React.useState('');
  const [weatherData, setWeatherData] = React.useState([]);

  const changeText = e => {
    setCountry(e.target.value)
  }

  const searchCountry = () => {
    fetch(`https://apiweatherhistory/dayone/country/${country}`).then(res => res.json().then(data => {
      setWeatherData(weatherData)
    }))
  }

  useEffect(() => {
    const svg = d3.select('.weatherSearch')
      .append('svg')
      .attr('width', 960)
      .attr('height', 500)
      .style('background', 'black');
  }, []);

  return (
    <div>
      <input type='text' onChange={(e) => changeText(e)} value={country} />
      <button onClick={searchCountry}>Search</button>
      <div className='weatherSearch' />
    </div>
  )
}

推荐阅读