React的setState分析
前端框架层出不穷,不过万变不离其宗,就是从MVC过渡到MVVM。从数据映射到DOM,angular中用的是watcher对象,vue是观察者模式,react就是state了。
React通过管理状态实现对组件的管理,通过this.state()方法更新state。当this.setState()被调用的时候,React会重新调用render方法来重新渲染UI。
本文针对React的SetState的源码来进行解读,根据陈屹老师的《深入React技术栈》加上自己的理解。
1. setState异步更新
setState通过一个队列机制实现state的更新。当执行setState时,会把需要更新的state合并后放入状态队列,而不会立刻更新this.state,利用这个队列机制可以高效的批量的更新state。
// 将新的 state 合并到状态更新队列中
var nextState = this._processPendingState(nextProps, nextContext);
// 根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件 var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext);
如果不通过setState而直接修改this.state的值,就像这样:this.state.value = 1,那么该state将不会被放入状态队列中,下次调用this.setState并对状态队列进行合并时,将会忽略之前直接别修改的state,因此我们应该用setState更新state的值。
2. setState循环调用
当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。
而performUpdateIfNecessary方法获取_pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用reciveComponent和updateComponent方法进行组件更新。
如果在shouldComponentUpdate或者componentWillUpdate方法中调用setState,此时this._pendingStateQueue != null,则perfromUpdateIfNecessary方法就会调用updateComponent方法进行组件更新,单updateComponent方法又会调用shouldComponentUpdate和componentWillUpdate方法,造成循环调用。

接下里我们看看setState的源码:
// 更新 state
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueSetState: function(publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState'
);
if (!internalInstance) {
return;
}
// 更新队列合并操作
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
// 如果存在 _pendingElement、_pendingStateQueue和_pendingForceUpdate,则更新组件
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
}
3. setState调用栈
对setState很了解是吧?来看看这个:
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
结果是:0、0、2、3,我第一次也不懂,怎么回事呢?
下面是一个简化的setState调用栈。

我们说过setState最终是通过enqueueUpdate执行state更新,enqueueUpdate代码如下:
function enqueueUpdate(component) {
ensureInjected();
// 如果不处于批量更新模式
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
dirtyComponents.push(component);
}
如果isBatchingUpdates也就是处于批量更新模式,就把当前调用了this.setState的组件放入dirtyComponents数组中。否则的话就不是批量更新模式,则对队列中的所有更新执行batchedUpdates方法。例子中的4次console之所以不同,这里的逻辑判断起了关键作用。
那batchingStrategy呢?其实他就是一个简单的对象:
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);
}
},
}
注意,batchedUpdates中有个transaction.perform调用,transaction,下面说。
4. transaction
有人看到这里,transaction,不就是事务吗?保证数据一致性要用到的,然后说了几条事务的特性,什么原子性、稳定性,但是抱歉,这里的事务和SQL里的事务不一样,我个人理解这个事务有点类似于中间件,为什么叫事务,不知道。
可以画一张图理解一下:

事务就是将需要执行的方法用wrapper封装起来,再通过事务提供的perform方法执行。而再perform之前,先执行所wrapeer中的initialize方法,执行完需要执行的方法后,再执行close方法。一组initialize和close方法称为一个wrapper,事务支持多个wrapper叠加。
React里涉及到很多高阶函数,个人理解这个事务也就是一个高阶函数嘛,也就是中间件的思想。
5. 解密setState
刚说了事务,那事务是怎么导致前面所述的setState的各种不同的输出呢?
很显然,我们可以将4次setState简单规成两类,componentDidMount是一类,setTimeOut中的又是一类,因为这两次在不同的调用栈中执行。
我们先看看在componentDidMount中setState的调用栈:

再看看在setTimeOut中的调用栈:

显然,在componentDidMount中调用setState的调用栈更加复杂,那我们重点看看在componentDidMount中的调用栈,发现了batchedUpdates方法,原来在setState调用之前,就已经处于batchedUpdates执行的事务之中了。
那batchedUpdates方法是谁调用的呢?我们再往上追溯一层,原来是ReactMount.js中的_renderNewRootComponent方法。也就是说,整个将React组件渲染到DOM的过程就处于一个大的事务中了。
接下来就可以理解了,因为在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设置为true了,所以两次setState的结果并没有立即生效,而是被放进了dirtyComponents中。这也解释了两次打印this.state.val都是0的原因,因为新的state还没被应用到组件中。
在看setTimeOut中的两次setState,因为没有前置的batchedUpdate调用,所以batchingStrategy的isBatchingUpdates标志位是false,也就导致了新的state马上生效,没有走到dirtyComponents分支。也就是说,setTimeOut中的第一次执行,setState时,this.state.val为1,而setState完成后打印时this.state.val变成了2。第二次的setState同理。
React的setState分析的更多相关文章
- React中setState同步更新策略
setState 同步更新 我们在上文中提及,为了提高性能React将setState设置为批次更新,即是异步操作函数,并不能以顺序控制流的方式设置某些事件,我们也不能依赖于this.state来计算 ...
- 初学React,setState后获取到的thisstate没变,还是初始state?
问题:(javascript)初学React,setState后获取到的thisstate没变,还是初始state?描述: getInitialState(){ return {data:[]}; } ...
- React中setState学习总结
react中setState方法到底是异步还是同步,其实这个是分在什么条件下是异步或者同步. 1.先来回顾一下react组件中改变state的几种方式: import React, { Compone ...
- React的setState学习及应用
React的setState学习及应用 一:作用: setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件.这是用于更新 ...
- React躬行记(16)——React源码分析
React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...
- 「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)
运用在React 中 setState的对象.数组的操作时是不能用类似array.push()等方法,因为push没有返回值,setState后会出现state变成Number,为了方便他人和自己查看 ...
- React中setState 什么时候是同步的,什么时候是异步的?
class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componen ...
- 前端每周清单第 49 期:Webpack 4 Beta 尝鲜,React Windowing 与 setState 分析,Web Worker 实战
前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点:分为新闻热点.开发教程.工程实践.深度阅读.开源项目.巅峰人生等栏目.欢迎关注[前端之巅]微信公众号(ID: fron ...
- React的setState执行机制
1. setState基本特点 1. setState是同步执行的 setState是同步执行的,但是state并不一定会同步更新 2. setState在React生命周期和合成事件中批量覆盖执行 ...
随机推荐
- Lambda表达式学习(2)
在. net3. 5里面 , 委托的定义和实现被大大的简化了!使用关键字Func或Action就可以定义一个委托 , 使用拉姆达表达式就可以实现一个具体的委托. Func关键字是用来定义一个有返回值的 ...
- 输入一批考生的的准考证号码,如果是 15 位,表示输入正确,否则重新输入。然后判断这个人的考试类别(号码中如果是以奇数结尾则考试类别为“A 类”,否则为“B 类”),最后输出此准考证的前 5 位和后 4 位,其他位用“*”来代替。说明:使用 StringBuffer 类的相关方法完成实验内容。
因为是一批考生,所以先创建一个字符数组存放一组准考证号. 此外这个程序涉及到包装类与基本数据类型的互相转换. string的substring方法,返回一个字符串是该字符串的子串.从第一个参数开始,第 ...
- JDK(九)JDK1.7源码分析【集合】HashMap的死循环
前言 在JDK1.7&1.8源码对比分析[集合]HashMap中我们遗留了一个问题:为什么HashMap在调用resize() 方法时会出现死循环?这篇文章就通过JDK1.7的源码来分析并解释 ...
- gulp插件 run-sequence(同步执行任务)
功能描述 gulp默认使用最大并发数执行任务,也就是说所有的任务几乎都是同时执行,而不会等待其它任务.但很多时候,任务是需要有先后次序的,比如要先清理目标目录,然后再执行打包. run-sequenc ...
- CANOPEN开发问题
我是一名研二的学生,现在教研室要开发canopen,我已经看了几个月了,在网上找了canopen的开源代码CANfestival,现在想做移植,有几个问题想要请教:1,开发主站,只买beckhoff的 ...
- centos7.4安装redis以及配置外网访问
一.安装redis 第一步:下载redis安装包 wget http://download.redis.io/releases/redis-4.0.6.tar.gz [root@VM_34_108_c ...
- Deepin15.8系统下安装QorIQ Linux SDK v2.0 yocto成功完美运行的随笔
2019.2.17日:最终安装成功,完美解决! 2019.2.16日:最终安装未成功,但是过程中排除 了几个bug,前进了几步,仅供参考. 写在最前面,yocto安装是有系统要求的,Deepin 15 ...
- 浏览器与go语言的websocket通信
简介WebSocket是HTML5一种新的协议.顾名思义,它在服务器和浏览器之间建立了全双工通信. 需求背景区块链测试系统web前端平台需要动态接收后端发送的状态信息改变一次测试session过程的状 ...
- react-native使用redux 存在的坑
前几天安卓真机测试的时候,突然发现一个严重的问题. 后退两次退出应用,再开启白屏.而杀掉进程后再开启就是好的. 这个重大bug我跟了好久,以为是splash-screen的问题. 后来一点一点打con ...
- 20155211 2016-2017-2 《Java程序设计》第九周学习总结
20155211 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十六章 整合数据库 一.JDBC入门 (一)JDBC简介 厂商在操作JDBC驱动程序时,依 ...