React:在什么情况下 useState 钩子中的 setState 会导致重新渲染?

React: Under what conditions does setState from the useState hook cause a re-render?

我有以下代码 (CodeSandbox):

function App() {
  const [blah, setBlah] = useState(true);
  console.log('BLAH', blah);
  setBlah(true);

  return <button onClick={() => setBlah(true)}>Blah</button>;
}

我了解到组件顶层的 setBlah(true) 导致了过多的重新渲染。我不明白的是,如果您注释掉顶层 setBlah(true) 并开始捣碎“Blah”按钮,为什么组件不会重新呈现?两者都一遍又一遍地将 blah 的状态设置为 true,但只有顶层 setBlah(true) 导致重新渲染。

来自 React docs on Bailing out of a state update:“请注意,React 在退出之前可能仍需要再次渲染该特定组件。”这里的关键词是“可能”,这对我来说意味着我的情况根据文档是有效的。所以问题可能会变成,在什么情况下调用 setState,使用已经设置的相同值,会导致重新渲染?知道这一点会很有用,因为人们可能想在他们的 setState 方法前面放置逻辑来检查该值是否与当前值相同,以免导致错误的重新渲染。

根据我的评论 - 因为您已经将 blah 的默认值设置为 true -

const [blah, setBlah] = useState(true);

按下按钮实际上并没有改变状态,因为它已经是真的。但是,如果您执行以下操作:

return <button onClick={() => setBlah(!blah)}>Blah</button>;

每次点击“Blah”按钮时,您都会在控制台中看到它在 true 和 false 之间切换。

这是

setBlah(true);

在导致循环的构造函数中。它已经将状态设置为 true,然后你告诉它在同一个构造函数中再次设置它。

通过 the documentation 您在问题中链接到:

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

你可以在那里读到 React 在比较以前和新的状态值时使用 Object.is。如果比较的 return 值为 true,那么 React 在考虑是否重新渲染时不会使用那个 setState 调用。这就是为什么在 onClick 处理程序中将状态值设置为 true 不会导致重新渲染。

也就是说,在任何组件的顶层无条件调用 setState 函数始终是错误的,因为它会(无限地)启动协调算法。在我看来,这是你问题的核心,如果你想了解React Fiber(React核心算法的实现),那么你可以从这里开始:https://github.com/acdlite/react-fiber-architecture

这是 React 文档中的另一个注释(需要为功能组件更新):

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.

https://reactjs.org/docs/react-component.html#componentdidupdate

解释 class 组件生命周期方法如何转化为功能组件超出了这个问题的范围(您可以在 Stack Overflow 上找到解决这个问题的其他问题和答案);但是,此指令适用于您的情况。

这是一个片段,显示当错误的 setState 调用被移除时,您的组件只呈现一次:

<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.16.3/babel.min.js"></script>

<div id="root"></div>

<script type="text/babel" data-type="module" data-presets="react">

const {useRef, useState} = React;

function Example () {
  const renderCountRef = useRef(0);
  renderCountRef.current += 1;

  const [bool, setBool] = useState(true);

  return (
    <div>
      <div>Render count: {renderCountRef.current}</div>
      <button onClick={() => setBool(true)}>{String(bool)}</button>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>