关于 setState
setState 的更新是同步还是异步,一直是人们津津乐道的话题。不过,实际上如果我们需要用到更新后的状态值,并不需要强依赖其同步/异步更新机制。在类组件中,我们可以通过this.setState的第二参数、componentDidMount、componentDidUpdate等手段来取得更新后的值;而在函数式组件中,则可以通过useEffect来获取更新后的状态。所以这个问题,其实有点无聊。
不过,既然大家都这么乐于讨论,今天我们就系统地梳理一下这个问题,主要分为两方面来说:
类组件(class-component)的更新机制
函数式组件(function-component)的更新机制
类组件中的 this.setState
在类组件中,这个问题的答案是多样的,首先抛第一个结论:
在legacy模式中,更新可能为同步,也可能为异步;
在concurrent模式中,一定是异步。
问题一、legacy 模式和 concurrent 模式是什么鬼?
通过ReactDOM.render(, rootNode)方式创建应用,则为 legacy 模式,这也是create-react-app目前采用的默认模式;
通过ReactDOM.unstable_createRoot(rootNode).render()方式创建的应用,则为concurrent模式,这个模式目前只是一个实验阶段的产物,还不成熟。
legacy 模式下可能同步,也可能异步?
是的,这不是玄学,我们来先抛出结论,再来逐步解释它。
当直接调用时this.setState时,为异步更新;
当在异步函数的回调中调用this.setState,则为同步更新;
当放在自定义 DOM 事件的处理函数中时,也是同步更新。
实验代码如下:
class StateDemo extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } render() { return
{this.state.count}
累加要解答上述现象,就必须了解 setState 的主流程,以及 react 中的 batchUpdate 机制。
首先我们来看看 setState 的主流程:
调用this.setState(newState);
newState会存入 pending 队列;
3,判断是不是batchUpdate;
4,如果是batchUpdate,则将组件先保存在所谓的脏组件dirtyComponents中;如果不是batchUpdate,那么就遍历所有的脏组件,并更新它们。
由此我们可以判定:所谓的异步更新,都命中了batchUpdate,先保存在脏组件中就完事;而同步更新,总是会去更新所有的脏组件。
非常有意思,看来是否命中batchUpdate是关键。问题也随之而来了,为啥直接调用就能命中batchUpdate,而放在异步回调里或者自定义 DOM 事件中就命中不了呢?
这就涉及到一个很有意思的知识点:react 中函数的调用模式。对于刚刚的 increase 函数,还有一些我们看不到的东西,现在我们通过魔法让其显现出来:
increase = () => { // 开始:默认处于bashUpdate // isBatchingUpdates = true this.setState({ count: t.........