为什么使用相同的值调用 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>
);
}
主要问题是,为什么登录函数组件体会导致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
仅当状态值因上次更新而实际更改时才会出现此问题。
在下面的例子中,当按钮被第一次点击时,“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>
);
}
主要问题是,为什么登录函数组件体会导致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