关于React setState的实现原理(三)
前面提到事务即将结束时,会去调用FLUSH_BATCHED_UPDATES的flushBatchedUpdates方法执行批量更新,该方法会去遍历dirtyComponents,对每一项执行performUpdateIfNecessary方法,该方法代码如下:
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
this._updateBatchNumber = null;
}
}
在我们的setState更新中,其实只会用到第二个 this._pendingStateQueue !== null 的判断,即如果_pendingStateQueue中还存在未处理的state,那就会执行updateComponent完成更新。那_pendingStateQueue是何时被处理的呢,继续看!
通过翻阅updateComponent方法,我们可以知道_pendingStateQueue是在该方法中由_processPendingState(nextProps, nextContext)方法做了一些处理,该方法传入两个参数,新的props属性和新的上下文环境,这个上下文环境可以先不用管。我们看看_processPendingState的具体实现:
_processPendingState: function (props, context) {
var inst = this._instance; // _instance保存了Constructor的实例,即通过ReactClass创建的组件的实例
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null; if (!queue) {
return inst.state;
} if (replace && queue.length === 1) {
return queue[0];
} var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
} return nextState;
},
什么replace啊什么的都可以暂时不用看,主要先看for循环内部做的事情,replace我们暂时认为是false。for循环遍历了_pendingStateQueue中所有保存的状态,对于每一个状态进行处理,处理时首先判断保存的是function还是object。若是function,就在inst的上下文中执行该匿名函数,该函数返回一个代表新state的object,然后执行assign将其与原有的state合并;若是object,则直接与state合并。注意,传入setState的第一个参数如果是function类型,我们可以看到,其第一个参数nextState即表示更新之前的状态;第二个参数props代表更新之后的props,第三个context代表新的上下文环境。之后返回合并后的state。这里还需要注意一点,这一点很关键,代码中出现了this._pendingStateQueue = null这么一段,这也就意味着dirtyComponents进入下一次循环时,执行performUpdateIfNecessary不会再去更新组件,这就实现了批量更新,即只做一次更新操作,React在更新组件时就是用这种方式做了优化。
好了,回来看我们的案例,当我们传入函数作为setState的第一个参数时,我们用该函数提供给我们的state参数来访问组件的state。该state在代码中就对应nextState这个值,这个值在每一次for循环执行时都会对其进行合并,因此第二次执行setState,我们在函数中访问的state就是第一次执行setState后已经合并过的值,所以会打印出2。然而直接通过this.state.count来访问,因为在执行对_pendingStateQueue的for循环时,组件的update还未执行完,this.state还未被赋予新的值,其实了解一下updateComponent会发现,this.state的更新会在_processPendingState执行完执行。所以两次setState取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是React为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。
接下来我们再来看看setState的第二个参数,回调函数,它是在什么时候执行的。
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
} componentDidMount() {
let me = this;
setTimeout(function() {
me.setState({count: me.state.count + 1}, function() {
console.log('did callback');
});
console.log('hello');
}, 0);
} componentDidUpdate() {
console.log('did update');
} render() {
return <h1>{this.state.count}</h1>
}
}
这个案例控制台打印顺序是怎样的呢?不卖关子了,答案是did update,did callback,hello。这里是在一个setTimeout中执行了setState,因此其处于一个单独的事务之中,所以hello最后打印容易理解。然后我们来看看setState执行更新时做了些啥。前面我们知道在执行完组件装载即调用了componentDidMount之后,事务开始执行一系列close方法,这其中包括调用FLUSH_BATCHED_UPDATES中的flushBatchedUpdates,我们来看看这段代码。
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
// updates enqueued by setState callbacks and asap calls.
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction); // 处理批量更新
ReactUpdatesFlushTransaction.release(transaction);
} if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll(); // 处理callback
CallbackQueue.release(queue);
}
}
};
可以看我做了中文标注的两个地方,这个方法其实主要就是处理了组件的更新和callback的调用。组件的更新发生在runBatchedUpdates这个方法中,下面的queue.notifyAll内部其实就是从队列中去除callback调用,因此应该是先执行完更新,调用componentDidUpdate方法之后,再去执行callback,就有了我们上面的结果。
总结
React在组件更新方面做了很多优化,这其中就包括了上述的批量更新。在componentDidMount中执行了N个setState,如果执行N次更新是件很傻的事情。React利用其独特的事务实现,做了这些优化。正是因为这些优化,才造成了上面见到的怪现象。还有一点,再使用this.state时一定要注意组件的生命周期,很多时候在获取state的时候,组件更新还未完成,this.state还未改变,这是很容易造成bug的一个地方,要避免这个问题,需要对组件生命周期有一定的了解。
关于React setState的实现原理(三)的更多相关文章
- 关于React setState的实现原理(二)
React中的Transaction 大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制.当所有的操作均执行成功,才会执行修改操 ...
- 关于React setState的实现原理(一)
前言 首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗? class Root extends React.Component { constru ...
- 并发之AQS原理(三) 如何保证并发
并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...
- React Native 入门到原理(详解)
抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲 ...
- The Road to learn React书籍学习笔记(第三章)
The Road to learn React书籍学习笔记(第三章) 代码详情 声明周期方法 通过之前的学习,可以了解到ES6 类组件中的生命周期方法 constructor() 和 render() ...
- 深入理解React:事件机制原理
目录 序言 DOM事件流 事件捕获阶段.处于目标阶段.事件冒泡阶段 addEventListener 方法 React 事件概述 事件注册 document 上注册 回调函数存储 事件分发 小结 参考 ...
- 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...
- word2vec原理(三) 基于Negative Sampling的模型
word2vec原理(一) CBOW与Skip-Gram模型基础 word2vec原理(二) 基于Hierarchical Softmax的模型 word2vec原理(三) 基于Negative Sa ...
- 动态修改JS对象的值及React setState
一.在JS里使用(非ES6) 实现场景: 给一个空对象填充某一指定数组内的值 并随机生成数量 const fruit = ['apple', 'banana', 'orange'] let fruit ...
随机推荐
- 创建WCF服务的过程
一.创建控制台WCF工程 1.创建一个控制台工程2.System.ServiceModel的引用3.可创建多个WCF服务,如:IService.cs和Service.cs 顺序:右键->添 ...
- (转)在GitHub多个帐号上添加SSH公钥
GitHub后台可以添加多个SSH Keys,但是同一个SSH Keys只能在添加在一个帐号上(添加时提示“Key is already in use”).理由很容易想到,SSH公钥使用时相当于用户名 ...
- Missing Number-[回溯][难]
2. Missing number 转自:https://mp.weixin.qq.com/s/WLRXLdi-3igkjtiWlHg7Ug Given a positive integer n(n≤ ...
- mysql性能测试--sysbench实践
mysql性能测试--sysbench实践 Sysbench 业界较为出名的性能测试工具 可以测试磁盘,CPU,数据库 支持多种数据库:oracle,DB2,MYSQL 需要自己下载编译安装 建议 ...
- java基础语法 数组
数组是相同数据类型元素的集合 数组本身是引用数据类型,即对象.但是数组可以存储基本数据类型,也可以存储引用数据类型. 在java中如果想要保存一组基本类型的数据,使用数组:如果想保存一组对象或者其 ...
- 在windows下MySQL-python的安装
安装MySQL-python下载文件PyMySQL-0.7.11.tar.gz 解压到任意目录 https://pypi.python.org/pypi/PyMySQL 然后在cmd命令行行下进行安装 ...
- 2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018) Solution
A. Altruistic Amphibians Upsolved. 题意: $有n只青蛙,其属性用三元组表示 <l_i, w_i, h_i> l_i是它能跳的高度,w_i是它的体重,h_ ...
- 居于mtk芯片安卓车机系统具体流程
一:车机系统框架 MCU 功能 电源控制 Radio 控制(RDS) 按键检测(Panel/Remote/SW) 常见信号检查(倒车/大灯/刹车) CAN 模块通讯 ARM- ...
- java 的异常处理
一.异常的概念: java 中的异常通常指的是在运行期出现的错误,这样的错误也是比较难以调试的,解决问题的时候注意观察出现错误的名字和行号最重要 下面看这个例子: import java.io.*; ...
- SpringBoot基础的使用
springboot的基础使用 和 内部原理 高级使用整合 进行web开发 springboot 看下spring的所有项目:https://spring.io/projects 等等 就不一一介绍了 ...