You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
请先看官方文档
上来先看官方文档中对setState()的定义
英文文档最佳
英文-React.Component - React
中文-React.Component - React
一、setState()的实践与问题
先看个最简单的问题,点击按钮后,count是加2吗?
为什么会只加1?
看官网这句话
重点是前两句,翻译过来就是
setState()并不总是立即更新组件,它可能会进行批处理或者推迟更新。这使得在调用setState()之后立即读取this.state成为一个潜在的隐患。
先直接抛出点击按钮加2的正确答案吧,下面两种方法都OK
二、setState源码世界
相信能到这里的同学都知道了setState()是个
既能同步又能异步
的方法了,那具体什么时候是同步的,什么时候是异步的?只有去源码里面看实现是最靠谱的方式。注:这里说的同步和异步只是“实现上看起来像同步还是异步,比如上面答案二setTimeout里面,看起来就是同步的”,实质上setState()是异步的
不管这里看不看得懂都没关系了,马上进入源码的世界。
1、如何快速查看react源码
上react的github仓库,直接clone下来
GitHub - facebook/react: A declarative, efficient, and flexible JavaScript library for building user interfaces.
到目前我看为止,最新的版本是16.2.0,我选了15.6.0的代码
一是为了参考前辈们的分析成果
二来,我水平有限,如果写的实在不清晰,同学们还可以参考着其他人的分析文章一起读,而不至于完全理解不了
如何切换版本?
1、找到对应版本号
2、复制15.6.0的历史记录号
3、回滚
如图,成功回滚到15.6.0版本
2、setState入口 => enqueueSetState
核心原则:既然是看源码,那当然就不是一行一行的读代码,而是看核心的思想,所以接下来的代码都只会放核心代码,旁枝末节只提一下或者忽略
setState()的入口文件在
src/isomorphic/modern/class/ReactBaseClasses.js
React组件继承自React.Component,而setState是React.Component的方法,因此对于组件来讲setState属于其原型方法
partialState顾名思义-“部分state”,这取名,意思大概就是不影响原来的state的意思吧
当调用setState()时实际上是调用了enqueueSetState方法,我们顺藤摸瓜(我用的是vscode的全局搜索),找到了这个文件
src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
这个文件导出了一个ReactUpdateQueue对象,“react更新队列”,代码名字起的好可以自带注释,说的就是这种大作吧,在这里注册了enqueueSetState方法
3、enqueueSetState => enqueueUpdate
先看enqueueSetState的定义
这里只需要关注internalInstance的两个属性
_pendingStateQueue:待更新队列
_pendingCallbacks: 更新回调队列
如果_pendingStateQueue的值为null,将其赋值为空数组[],并将partialState放入待更新state队列_pendingStateQueue。最后执行enqueueUpdate(internalInstance);
接下来看enqueueUpdate
它执行的是ReactUpdates的enqueueUpdate方法
这个文件刚好就在旁边,不用找了
src/renderers/shared/stack/reconciler/ReactUpdates.js
找到enqueueUpdate方法
enqueueUpdate方法定义
这段话对于理解setState非常重要
判断batchingStrategy.isBatchingUpdates
batchingStrategy是批量更新策略,isBatchingUpdates表示是否处于批量更新过程,开始默认值为false
上面这句话的意思是:
如果处于批量更新模式,也就是isBatchingUpdates为true时,不进行state的更新操作,而是将需要更新的component添加到dirtyComponents数组中
如果不处于批量更新模式,对所有队列中的更新执行batchedUpdates方法,往下看下去就知道是用事务的方式批量的进行component的更新,事务在下面。
借用《深入React技术栈》Page167中一图
4、核心:batchedUpdates => 调用transaction
那batchingStrategy.isBatchingUpdates又是怎么回事呢?看来它才是关键
但是,batchingStrategy 对象并不好找,它是通过 injection 方法注入的,一番寻找,发现了 batchingStrategy 就是 ReactDefaultBatchingStrategy。
src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
具体怎么找文件,又属于另一个范畴了,我们今天只专注 setState,其他的容后再说吧
相信部分同学在这里已经有些迷糊了,没关系,再坚持一下,旁枝末节先不管,只知道我们找到了核心方法batchedUpdates,马上要胜利了,别放弃(我第一次看也是这样熬过来的,一遍不行就两遍,大不了看多几遍)
先看批量更新策略-batchingStrategy,它到底是什么
终于找到了,isBatchingUpdates属性和batchedUpdates方法
如果isBatchingUpdates为true,当前正处于更新事务状态中,则将Component存入dirtyComponent中,
否则调用batchedUpdates处理,发起一个transaction.perform()
所有的 batchUpdate 功能都是通过执行各种 transaction 实现的
这是事务的概念,先了解一下事务吧
5、事务
这一段就直接引用书本里面的概念吧,《深入React技术栈》Page169
下面这段代码应该能帮助理解
6、核心分析:batchingStrategy 批量更新策略
回到batchingStrategy:批量更新策略,再看看它的代码实现
可以看到isBatchingUpdates的初始值是false的,在调用batchedUpdates方法的时候会将isBatchingUpdates变量设置为true。然后根据设置之前的isBatchingUpdates的值来执行不同的流程
还记得上面说的很重要的那段代码吗
首先,点击事件的处理本身就是在一个大的事务中(这个记着就好),isBatchingUpdates已经是true了
调用setState()时,调用了ReactUpdates.batchedUpdates用事务的方式进行事件的处理
在setState执行的时候isBatchingUpdates已经是true了,setState做的就是将更新都统一push到dirtyComponents数组中;
在事务结束的时候才通过 ReactUpdates.flushBatchedUpdates 方法将所有的临时 state merge 并计算出最新的 props 及 state,然后将批量执行关闭结束事务。
到这里我并没有顺着ReactUpdates.flushBatchedUpdates方法讲下去,这部分涉及到渲染和Virtual Dom的内容,反正你知道它是拿来执行渲染的就行了。
到这里为止,setState的核心概念已经比较清楚了,再往下的内容,暂时先知道就行了,不然展开来讲一环扣一环太杂了,我们做事情要把握核心。
到这里不知道有没有同学想起一个问题
isBatchingUpdates 标志位在 batchedUpdates 发起的时候被置为 true ,那什么时候被复位为false的呢?
还记得上面的事务的close方法吗,同一个文件
src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
相信眼尖的同学已经看到了,close的时候复位,把isBatchingUpdates设置为false。
通过原型合并,事务的close 方法,将在 enqueueUpdate 执行结束后,先把 isBatchingUpdates 复位,再发起一个 DOM 的批更新
到这里,我们会发现,前面所有的队列、batchUpdate等等都是为了来到事务的这一步,前面都只是批收集的工作,到这里才真正的完成了批更新的操作。
7、再回到题目
这两段代码
第一种情况,在执行第一个setState时,本身已经处于一个点击事件触发的这个大事务中,已经触发了一个batchedUpdates,isBatchingUpdates为true,所以两个setState都会被批量更新,这时候属于异步过程,this.state并没有立即改变,执行setState只是相当于把partialState(前面说的部分state)传入dirtyComponents,最后在事务的close阶段执行flushBatchedUpdates去重新渲染。
第二种情况,有了setTimeout,两次setState都会在点击事件触发的大事务中的批量更新batchedUpdates结束之后再执行,所以他们会触发两次批量更新batchedUpdates,也就会执行两个事务和函数flushBatchedUpdates,就相当于同步更新的过程了。
参考
React技术内幕 setState的秘密 - 掘金
React源码分析 - 组件更新与事务 - 掘金
源码看React setState漫谈(一) - 前端成长之路 - SegmentFault 思否
源码看React setState漫谈(二) - 前端成长之路 - SegmentFault 思否
The text was updated successfully, but these errors were encountered: