这段代码的输出结果,第一个console.log会输出data,而第二个console.log会输出setTimeout。也就是第一次setState的时候,是异步更新的,而第二次setState的时候,它又变成了同步更新,是不是有点晕呢?我们去源码里看一下setState更新调度的时候到底做了些什么。
探针
setState被调用后最终会走到scheduleUpdateOnFiber函数中,那我们看一下这个函数做了些什么呢?
typescript
if(executionContext ===NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue();}
executionContext代表了React目前所处的阶段,而NoContext你可以理解为是React没活干的状态,而flushSyncCallbackQueue里面就会去同步调用我们的this.setState,也就是说同步更新我们的state。所以,我们已经知道了,当executionContext为NoContext的时候,我们的setState就是同步的。那什么地方会改变executionContext的值呢?
我们随便找几个地方看看:
typescript
functionbatchedEventUpdates$1(fn, a) {varprevExecutionContext = executionContext;executionContext |=EventContext;// ...省略}functionbatchedUpdates$1(fn, a) {varprevExecutionContext = executionContext;executionContext |=BatchedContext;// ...省略}
当React进入它自己的调度步骤时,会给executionContext赋予不同的枚举,表示不同的操作和目前React所处的调度状态,而executionContext的初始值就是NoContext,所以只要你不进入React的调度流程,这个值就是NoContext,那你的setState就是同步的。
那在useState呢?自从React出了hooks之后,函数组件也能拥有自己的状态,那么如果我们调用它的第二个参数去setState更改状态,和类组件的this.setState是一样的效果吗?
没错,因为useState的set函数最终也会走到scheduleUpdateOnFiber,所以在这一点上和this.setState是没有区别的,相当于使用了一个通用函数。
但是值得注意的是,当我们调用this.setState的时候,React会自动帮我们做一个state的合并,而hook则不会,所以我们在使用的时候更着重注意这一点。
举个例子:
patek-hzs.jshdwatch.com
patek-bjs.xajshd.com
patek-sz.kmjshd.com
patek-hzs.hebjshd.com
patek-bjs.watchrft.cn
patekw.jsfltime.com
patekw.watchec.cn
patekw.nnjshd.com
patekw.richardweixiu.com
patekw.ytjshd.com
patekw.watchjwj.cn
patekw.swatchkb.top
patekw.ywbzn.com
patek-cds.hidcwatch.com
patek-njs.ywbzn.com
patek-beijing.watch4s.com
patek-beijing.wbiaohome.com
patek-beijing.wbiao120.com
constantin-shs.fdcpx.net
constantin-bjs.fdcpx.net
constantin-shenzhen.biaoshouhou.cn
constantin-gzs.biaoshouhou.cn
constantin-shs.audemarsweixiu.com
constantin-bjs.audemarsweixiu.com
constantin-shenzhen.hidcwatch.com
constantin-gzs.hidcwatch.com
constantin-shs.fjfsx.com
constantin-bjs.fjfsx.com
constantin-shenzhen.hntwx.cn
constantin-gzs.hntwx.cn
constantin-shs.hx626.com
constantin-bjs.hx626.com
constantin-shenzhen.watchjwf.cn
constantin-gzs.watchjwf.cn
constantin-shs.shjshdzb.com
constantin-bjs.shjshdzb.com
constantin-shenzhen.shmwatch.cn
constantin-gzs.shmwatch.cn
constantin-shs.gyjshd.com
constantin-bjs.gyjshd.com
constantin-shenzhen.zhcxb.cn
constantin-gzs.zhcxb.cn
constantin-shenzhen.jshdvip.com
constantin-gzs.jshdvip.com
constantin-shs.gyjshdzb.com
constantin-bjs.gyjshdzb.com
constantin-sys.jhpwd.cn
constantin-zzs.jhpwd.cn
constantin-shenzhen.wzjshd.com
constantin-gzs.wzjshd.com
typescript
// 类组件中state = {data:"data",data1:"data1",};this.setState({data:"new data"});console.log(state);// { data: 'new data',data1: 'data1' }// 函数组件中const[state, setState] =useState({data:"data",data1:"data1"});setState({data:"new data"});console.log(state);// { data: 'new data' }
但是如果你自己去尝试在函数组件中的setTimeout中去调用setState之后,打印state,你会发现并没有改变,这时你就会很疑惑,为什么呢?这不是同步执行的么?这其实是一个闭包问题,实际上拿到的还是上一个state,那打印出来的值自然也还是上一次的,此时真正的state已经改变了。
相信看到这里对于标题你已经有了答案了吧?只要你进入了React的调度流程,那就是异步的。只要你没有进入React的调度流程(executionContext === NoContext),那就是同步的。什么情况不会进入React的调度流程?setTimeout、setInterval,直接在DOM上绑定原生事件等。这些都不会走React的调度流程,你在这种情况下调用setState,那这次setState就是同步的。否则就是异步的。而setState同步执行的情况下,DOM也会被同步执行更新,也就意味着如果多次setState会导致多次更新,这也是毫无意义且浪费性能的。