React:useState 钩子中的 setState 在啥情况下会导致重新渲染?
Posted
技术标签:
【中文标题】React:useState 钩子中的 setState 在啥情况下会导致重新渲染?【英文标题】:React: Under what conditions does setState from the useState hook cause a re-render?React:useState 钩子中的 setState 在什么情况下会导致重新渲染? 【发布时间】:2021-12-27 23:04:48 【问题描述】:我有以下代码 (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
方法前面,以检查值是否与当前值相同,以免导致错误的重新渲染。
【问题讨论】:
每次调用setBlah()
都会导致重新渲染。使用您原来的 sn-p,您在每次渲染时都调用 setBlah()
,这会使您无限循环。另一个 setBlah()
调用是由事件触发而不是在渲染时触发,这就是它不会导致相同循环的原因。
是的,我并不是说onClick
的setBlah
应该导致相同的循环——它不应该。我是说它不会导致任何重新渲染发生,甚至一次都没有。
由于您已经将 blah 的默认值设置为 true - const [blah, setBlah] = useState(true)
- 捣碎按钮实际上并没有改变状态,因为它已经是 true。我认为:)
完全是这样,但是为什么这与***setBlah(true)
不同,并且在值不变时会导致无休止的重新渲染?如果*** setBlah(true)
导致重新渲染,它会无休止地重新渲染,我希望按钮单击会导致一次重新渲染。
【参考方案1】:
来自我的评论 - 因为您已经将 blah 的默认值设置为 true -
const [blah, setBlah] = useState(true);
捣碎按钮实际上并没有改变状态,因为它已经是真的了。但是,如果您执行以下操作:
return <button onClick=() => setBlah(!blah)>Blah</button>;
每次点击“Blah”按钮时,您都会在控制台中看到它在真假之间切换。
这是
setBlah(true);
在导致循环的构造函数中。它已经将 state 设置为 true,然后你告诉它在同一个构造函数中再次设置它。
【讨论】:
【参考方案2】:通过您在问题中链接到的the documentation:
如果您将 State Hook 更新为与当前状态相同的值,React 将退出而不渲染子级或触发效果。 (React 使用
Object.is
comparison algorithm。)
你可以在那里读到 React 在比较以前和新的状态值时使用Object.is
。如果比较的返回值为true
,那么React 不会使用setState
调用来考虑是否重新渲染。这就是为什么在 onClick
处理程序中将状态值设置为 true
不会导致重新渲染。
也就是说,在任何组件的顶层无条件调用setState
函数始终是错误的,因为它会启动协调算法(无限)。在我看来这是你问题的核心,如果你想了解 React Fiber(React 核心算法的实现),那么你可以从这里开始:https://github.com/acdlite/react-fiber-architecture
这是 React 文档中的另一条注释(需要针对功能组件进行更新):
您可以立即在
componentDidUpdate()
中调用setState()
,但请注意,它必须像上面的示例一样包含在条件中,否则您将导致无限循环。↳https://reactjs.org/docs/react-component.html#componentdidupdate
解释类组件生命周期方法如何转换为功能组件超出了此问题的范围(您可以在 Stack Overflow 上找到解决此问题的其他问题和答案);但是,此指令适用于您的情况。
这是一个 sn-p,显示当错误的 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>
【讨论】:
当您在组件的顶层说setState
“启动协调算法”时,这不是setState
在onClick
处理程序中所做的吗?我本来预计不会发生任何和解,因为我会想象setState
在这两种情况下都会短路,因为值与传递给useState
的默认值相同,但这显然不会发生在顶部级别setState
,而它似乎发生在onClick
的setState
。
我认为您混淆了渲染和协调算法;她们不一样。渲染可以作为协调算法的结果发生,但并不总是发生。当您调用 setState
函数时(无论在哪里),它都会启动协调算法,然后再次调用您的组件函数。因此,如果您在组件的顶层调用 setState
,您将在运行时创建一个无限循环。
我认为您的大多数其他问题都可以通过阅读我在回答中链接到的 React 光纤架构的描述性摘要来回答。而且,与往常一样,您可以通过查看 React 的源代码来找到最清晰的内容;这是 React v17.0.2
中 useState
钩子的链接:github.com/facebook/react/blob/17.0.2/packages/react/src/…。
我已经用 React 文档中关于有条件地调用 setState
的附加引用更新了我的答案。
@Vampiro 如果这回答了您的问题,请随时将其标记为这样,以便其他人更容易找到它。以上是关于React:useState 钩子中的 setState 在啥情况下会导致重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章
ReactJS - 新的 useState React 钩子中的 prevState?
如何为 React 钩子(useState 等)做流类型注释?
在对数组进行排序并传递它之后,React 钩子 useState/setState 不起作用
React - useState 钩子第一次和后续设置状态时的奇怪行为