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

Why does calling useState's setter with the same value subsequently trigger a component update even if the old state equals the new state?

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

在下面的例子中,当按钮被第一次点击时,“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>
  );
}

Codesandbox example

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

答案隐藏在React docs的某处:

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.

没什么新鲜事,但是:

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree.

但是注意useEffect API定义:

will run after the render is committed to the screen.

如果您在 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" 日志)将有一个额外的“Bail out”调用,但是 React 不会“更深入”,也不会更改现有的 JSX 或状态(不会记录额外的 "B")。

除了普遍接受的正确答案之外,我还发现了更深入地研究该问题的内容:

其中实际上有更复杂的机制。

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

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

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

我在codesandbox

中做了一个demo of that