简体   繁体   English

初始渲染时 useState 反应钩子的意外行为

[英]Unexpected behavior of useState react hook at initial render

I am building a simple app that shows data using d3 graph by searched input value.我正在构建一个简单的应用程序,该应用程序通过搜索的输入值使用 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>
  )
}

When the component is mounted, d3 appends svg element on div tag name weatherSearch .安装组件后,d3 在 div 标签名称weatherSearch上附加 svg 元素。

Problem is the component renders twice therfore svg tag is drawn twice as well.问题是组件渲染两次,然后 svg 标记也被绘制两次。 But when I comment out two states initialization,但是当我注释掉两个状态初始化时,

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

the component renders only once and draw svg tag one time.该组件仅呈现一次并绘制一次 svg 标记。

input or click interaction did not happen yet which means two state change methods (setCountry, setWeatherData) are not even used.输入或点击交互尚未发生,这意味着甚至没有使用两个 state 更改方法(setCountry、setWeatherData)。

and Search component is the root component of index.js so there is no way parent component triggers rerendering.并且 Search 组件是 index.js 的根组件,因此父组件无法触发重新渲染。

Why is this happening?为什么会这样? Only initialize states could trigger rerendering?只有初始化状态才能触发重新渲染?

I suspect it is because appending the svg is in the functional component body so it will get executed every time the component is "rendered".我怀疑这是因为附加 svg 位于功能组件主体中,因此每次“渲染”组件时都会执行它。 By "render" I mean, when the react framework renders the virtualDOM tree to compute diffs during the "render" phase.我所说的“渲染”是指,当 React 框架在“渲染”阶段渲染 virtualDOM 树以计算差异时。 This is different from the "commit" phase when the computed DOM is flushed to the actual DOM and when effects and others lifecycle functions run.这与计算 DOM 刷新到实际DOM 以及效果和其他生命周期函数运行时的“提交”阶段不同。 The "render" phase can be paused, aborted, and restarted by react nearly any number of times. “渲染”阶段可以通过几乎任意次数的反应来暂停、中止和重新启动。

在此处输入图像描述

I would place this logic in a useEffect hook with an empty dependency array so it runs on component mount.我会将这个逻辑放在一个带有空依赖数组的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>
  )
}

You can not say for sure how many times the component will be rerendered.您无法确定组件将被重新渲染多少次。 It is not a deterministic algorithm, it's heuristic.它不是确定性算法,而是启发式算法。

Also, it is not a good idea to mix up controlled virtual DOM with uncontrolled D3.此外,将受控虚拟 DOM 与不受控制的 D3 混为一谈也不是一个好主意。 Please look at this hook https://en.reactjs.org/docs/hooks-reference.html#uselayouteffect and this API https://en.reactjs.org/docs/portals.html to understand how to implement such cases. Please look at this hook https://en.reactjs.org/docs/hooks-reference.html#uselayouteffect and this API https://en.reactjs.org/docs/portals.html to understand how to implement such cases.

But IMHO it is better to make SVG part controlled by React without D3.但恕我直言,最好让 SVG 部分由 React 控制而不使用 D3。 You can use SVG tags directly in virtual DOM.您可以直接在虚拟 DOM 中使用 SVG 标签。

Additional tips:附加提示:

Handlers could be cached by useCallback .处理程序可以由useCallback缓存。 For example:例如:

const onChange = useCallback(
  ({ target: { value } }) => setCountry(value),
  [setCountry],
);

<input onChange={onChangeText} />   

Also, it is better to name handlers onSomeEvent此外,最好将处理程序命名为onSomeEvent

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM