第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的?
在 React 中,如果是由 React 引发的事件处理(比如通过 onClick 引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的 setState 调用会同步执行 this.state 。所谓 除此之外,指的是绕过 React 通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval 产生的异步调用。
原因: 在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由React控制的事件处理过程 setState 不会同步更新this.state。
注意: setState 的 异步 并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的 异步,当然可以通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。
详细请看 深入 setState 机制
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
react 18 发布之后,估计要对不同版本不同模式进行不同的回答了
为什么setTimeout里面的setState就不能进行批处理?如果批处理会有什么异常,望解答。
setState是异步的,setState之后,取state中的值并不一定是最新更新的值。
这样设计的原因是可能在同步的代码中可能存在连续的多个setSate操作,react会对他们进行智能的合并,直到执行到了最后一个setState,React才回智能的合并state的,并异步的设置state的值,后续判断是否进行render操作.如果同步的每次setState都去走一遍reconciler & commit操作,则太耗性能了
我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.
所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.
setState是同步还是异步?
此处
同步异步
的定义我们知道
Promise.then()
,setTimeout
是异步执行. 从js执行来说,setState
肯定是同步执行.所以这里讨论的同步和异步并不是指
setState
是否异步执行, 而是指调用setState
之后this.state
能否立即更新.分析
setState({item: 'new xxx'})
之后, 会将传入setState
的参数包装成一个update对象
并添加到updateQueue
队列中.updateQueue
队列在什么时机被合并到this.state
中才是本题目的关键. 因为合并之后this.state
必然就已经更新了.到这里问题转化为调用
setState
之后, 是否立即触发scheduler调度?this.state
必然能同步获取.this.state
不能同步获取.setState
都会进行一次scheduler调度(可以参考React 调度机制).在最新源码v16.14.0中体现为调用
ensureRootIsScheduled
. 在该源码中, 可以得到回答本题目的最佳答案, 核心逻辑如下:结论
executionContext
的状态.executionContext
为空时, 表现为同步.executionContext
不为空, 表现为异步.executionContext
何时为空?这个问题反过来更好理解, 什么时候
executionContext
不为空? 因为executionContext
是react内部控制的属性, 当初次render, 合成事件触发时都会改变executionContext
的值.executionContext
的逻辑, 就能保证executionContext
为空, 进而实现setState
为同步.executionContext
), 在原生事件的回调函数中执行 setState 就是同步的附加条件
以上分析都是基于
legacy
模式进行分析的, 众所周知react即将(可能)全面进入concurrent
模式(可以参考react 启动模式). 在concurrent
模式下, 这个题目可能就没有意义了, 因为从目前最新代码来看, 在concurrent
模式下根本就不会判断executionContext
, 所以concurrent
模式下setState都为异步.演示示例
对于此问题在codesandbox上面做了一个demo(详细演示示例). 在
legacy
和concurrent
模式下演示并验证了上述结论.先说结论:
如果是通过
DOM 事件
(例如:onClick) 的事件处理函数
调用了 setState 函数,导致 React 进行更新(或者说“调度”scheduleWork),则此时事件处理函数
中的 setState 的行为是异步的(也就是合并更新)。上面的评论中说了很多 setState “异步”的情况。我这里补充一下“同步”的情况,下面的例子使用了 setTimeOut,组件最后显示结果是
33
源码:
从源码的角度来说,setState 的行为是“异步”还是“同步”取决于 React 执行 setState 方法时的
执行上下文
①(ExecutionContext)。如果 ExecutionContext 为 0,表示当前没有正在进行的其他任务,则 setState 是“同步”的。React 源码地址:https://github.com/facebook/react/blob/b53ea6ca05d2ccb9950b40b33f74dfee0421d872/packages/react-reconciler/src/ReactFiberWorkLoop.js
①注意这里的
执行上下文
不是浏览器的,为了更好的控制渲染任务,避免长时间占用浏览器的主线程, React 实现了自己的执行上下文
。React的setState本身并不是异步的,是因为其批处理机制给人一种异步的假象。
【React的更新机制】
生命周期函数和合成事件中:
原生事件和异步代码中:
总结:
react会表现出同步和异步的现象,但本质上是同步的,是其批处理机制造成了一种异步的假象。(其实完全可以在开发过程中,在合成事件和生命周期函数里,完全可以将其视为异步)
异步更新,同步执行
单独的说同步异步没有意义,一般配合场景:
setState后取值
使用的,初学者经常发现setState后值还是旧的,而且这个错误很难发现。由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。
React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
React Dan Abramov 大佬的解释
这题应该是想问
setState
的 reconiliation 特性@yygmind 被这问题吓到了,还好点进来看了,你做出了我放心的解释
我的个人观点是这样的对于同步或者是异步都可以进行控制的想要同步就同步想要异步就异步
我老婆提醒我如果面试官问什么你管不了 谁让你想去那工作呢
我们平时写的时候就当它是异步进行就好了。
这道题和19题有些类似。#18 (comment)
你是想说明什么,没太明白。
你的意思难道是从你截图给的这个可以看出reactsetState是或者部分是异步的?
---补充---
或者你的意思难道是标题是《何时setState是异步的》, 就断定 “setState确实可能是异步的”
这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。
这里的异步指的是多个state会合成到一起进行批量更新。
希望初学者不要被误导