为什么即使旧状态等于新状态,使用相同的值调用 useState 的 setter 也会随后触发组件更新?

IT技术 javascript reactjs react-hooks react-dom
2021-05-13 18:51:51

仅当状态值因上次更新而实际更改时,才会出现此问题。

在下面的例子中,当第一次点击按钮时,“setState”被调用了一个新的值(12),并且发生了一个组件更新,这是可以理解的。

当我第二次单击同一个按钮时,将状态设置为相同的 12 值会导致组件重新运行(重新渲染),为什么会发生这种情况是我的主要问题。

任何后续设置为 12 相同值的 setState 都不会触发组件更新,这也是可以理解的。12 === 12 所以不需要更新。

那么,为什么第二次单击按钮时会发生更新?

export default function App() {
  const [state, setState] = useState(0);

  console.log("Component updated");

  return (
    <div className="App">
      <h1>Hello CodeSandbox {state}</h1>
      <button onClick={() => setState(12)}>Button</button>
    </div>
  );
}

代码沙盒示例

2个回答

主要问题是,为什么登录函数组件主体会导致 3 个日志"Component updated"

答案隐藏在React 文档中的某处

如果您将 State Hook 更新为与当前状态相同的值,React 将退出而不渲染子项或触发效果。

没有什么新东西,但是:

请注意,在退出之前,React可能仍需要再次渲染该特定组件。这不应该是一个问题,因为 React 不会不必要地“深入”到树中。

但请注意useEffectAPI 定义:

将在渲染提交到屏幕后运行。

如果您记录更改,useEffect您只会注意到预期的两个“B”日志,这正是提到的救助行为示例:

const App = () => {
  const [state, setState] = React.useState(0);

  useEffect(() => {
    console.log("B");
  });

  console.log("A");

  return (
    <>
      <h1>{state}</h1>
      <button onClick={() => setState(42)}>Click</button>
    </>
  );
};

组件将有一个额外的“救助”调用App(额外的“A”日志),React 不会“深入”并且不会更改现有的 JSX 或状态(不会记录额外的“B”)。

编辑 Q-65037566-检查渲染

除了普遍接受的正确答案之外,我还发现在这个问题上有更深入的研究:

实际上有更复杂的机制。

实际上,任何 setState 调用都在幕后应用了 reducer 函数,并且该 reducer 函数在下一次 useState 调用时运行,而不是在组件函数执行之前运行。

因此,如果不执行该减速器(在 useState 调用上),通常无法知道新状态是否相同。

然而,另一方面,当这样的 reducer 被执行一次并且状态没有改变时,组件的返回将被忽略(跳过渲染)并且该 reducer 的下一次调用将在组件的函数之前执行,而不是在 useState 调用时执行。对于组件生命周期的第一个 setState 也是如此。

我已经在 codeandbox 中做了一个演示