React-setState源码的理解
首先举一个最简单的例子:
this.state={
a:1
}
this.setState({
a:2
})
console.log(this.state.a)
//1
可以说setState()操作是一个异步,因为要将一段时间内的state改变压入栈,并最终执行一次,同时也是优化性能的一部份
但是:
定时器:
定时器中的setState,每次都会引起新的render,即使是同一个定时器中的多次setState,
因为定时器中的回调不属于react控制
原生事件:


下面简单讲解一下源码的理解
入口:
ReactComponent.prototype.setState = function (partialState, callback) {
// 将setState事务放入队列中
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
partialState:部分state,传入enqueueSetState(),如果有回调函数,则调用enqueueCallback(),在更改后执行
enquenueSetState:
enqueueSetState: function (publicInstance, partialState) {
// 先获取ReactComponent组件对象
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
// 如果_pendingStateQueue为空,则创建它。可以发现队列是数组形式实现的
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 将要更新的ReactComponent放入数组中
enqueueUpdate(internalInstance);
}
通过getInternalInstanceReadyForUpdate()获取到ReactComponent组件对象,
判断这个组件的state等待队列是否为空,为空则创建为空数组,并将state push进这个数组,
将要更新的组件传入enqueueUpdate()并执行
getInternalInstanceReadyForUpdate
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
// 从map取出ReactComponent组件,还记得mountComponent时把ReactElement作为key,将ReactComponent存入了map中了吧,ReactComponent是React组件的核心,包含各种状态,数据和操作方法。而ReactElement则仅仅是一个数据类。
var internalInstance = ReactInstanceMap.get(publicInstance);
if (!internalInstance) {
return null;
}
return internalInstance;
}
publicInstance:enqueueSetState方法传入的this,指当前组件实例
从map取出ReactComponent组件,还记得mountComponent时把ReactElement作为key,
将ReactComponent存入了map中了吧,ReactComponent是React组件的核心,包含各种状态,数据和操作方法。而ReactElement则仅仅是一个数据类
注:(virtual DOM 就是由一个个的React element 组成。React element, 是由React 库提供的createElement 函数创建而来)
enqueueUpdate:
function enqueueUpdate(component) {
ensureInjected();
// 如果不是正处于创建或更新组件阶段,则处理update事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中
dirtyComponents.push(component);
}
基本逻辑:如果现在是正在创建或者更新组件的阶段,则把组件放入dirtyComponents数组中,并不先update,否则就进行batchedUpdates()
enqueueUpdate包含了React避免重复render的逻辑。mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了
。(——可以不看
之后React以事务的方式处理组件update,事务处理完后会调用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES这个wrapper,故最终会调用RESET_BATCHED_UPDATES.close(),
——)
它最终会将isBatchingUpdates设置为false。这个过程比较麻烦,想更清晰的了解的话,建议自行分析源码。
故getInitialState,componentWillMount, render,componentWillUpdate 中setState都不会引起updateComponent。但在componentDidMount和componentDidUpdate中则会。
batchedUpdates:
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 批处理最开始时,将isBatchingUpdates设为true,表明正在更新
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
// 以事务的方式处理updates,后面详细分析transaction
transaction.perform(callback, null, a, b, c, d, e);
}
}
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
// 事务批更新处理结束时,将isBatchingUpdates设为了false
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
设置两个wrapper,
RESET_BATCHED_UPDATES设置isBatchingUpdates,

FLUSH_BATCHED_UPDATES会在一个transaction的close阶段运行runBatchedUpdates,从而执行update

事务transaction
不详述,百度很多文章,大体思路:
- 初始化:事务初始化阶段没有注册方法,故无方法要执行
- 运行:执行setSate时传入的callback方法,一般不会传callback参数
- 结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法
runBatchedUpdates:
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
// dirtyComponents中取出一个component
var component = dirtyComponents[i];
// 取出dirtyComponent中的未执行的callback,下面就准备执行它了
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
if (component._currentElement.props === component._renderedComponent._currentElement) {
namedComponent = component._renderedComponent;
}
}
// 执行updateComponent
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
// 执行dirtyComponent中之前未执行的callback
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
runBatchedUpdates循环遍历dirtyComponents数组,主要干两件事。
首先执行performUpdateIfNecessary来刷新组件的view,
然后执行之前阻塞的callback。
下面来看performUpdateIfNecessary。
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// receiveComponent会最终调用到updateComponent,从而刷新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 执行updateComponent,从而刷新View。这个流程在React生命周期中讲解过
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
},
执行updateComponent进行状态更新,值得注意的是更新操作内会调用_processPendingState进行原有state的合并以及设置this._pendingStateQueue = null,这也就意味着dirtyComponents进入下一次循环时,执行performUpdateIfNecessary不会再去更新组件
总结:
setState流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程如下
- enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
- 如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
- batchedUpdates发起一次transaction.perform()事务
- 开始执行事务初始化,运行,结束三个阶段
- 初始化:事务初始化阶段没有注册方法,故无方法要执行
- 运行:执行setSate时传入的callback方法,一般不会传callback参数
- 结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法
- FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。
推荐完整地址:https://yq.aliyun.com/articles/72329?t=t1
React-setState源码的理解的更多相关文章
- 《React Native 精解与实战》书籍连载「React Native 源码学习方法及其他资源」
此系列文章将整合我的 React 视频教程与 React Native 书籍中的精华部分,给大家介绍 React Native 源码学习方法及其他资源. 最后的章节给大家介绍 React Native ...
- React Fiber源码分析 (介绍)
写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法 ...
- storm源码之理解Storm中Worker、Executor、Task关系 + 并发度详解
本文导读: 1 Worker.Executor.task详解 2 配置拓扑的并发度 3 拓扑示例 4 动态配置拓扑并发度 Worker.Executor.Task详解: Storm在集群上运行一个To ...
- React的React.createContext()源码解析(四)
一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...
- React的React.createElement源码解析(一)
一.什么是jsx jsx是语法糖 它是js和html的组合使用 二.为什么用jsx语法 高效定义模版,编译后使用 不会带来性能问题 三.jsx语法转化为js语法 jsx语法通过babel转化为 ...
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- 精读《React PowerPlug 源码》
1. 引言 React PowerPlug 是利用 render props 进行更好状态管理的工具库. React 项目中,一般一个文件就是一个类,状态最细粒度就是文件的粒度.然而文件粒度并非状态管 ...
- React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom中去(矛头指向fiber,fiber不解读这个过程也不知道)
一.ReactDOM.render 都干啥了 我们在写react的时候,最后一步肯定是 ReactDOM.render( <div> <Home name="home&qu ...
- React 16 源码瞎几把解读 【二】 react组件的解析过程
一.一个真正的react组件编译后长啥样? 我们瞎几把解读了react 虚拟dom对象是怎么生成的,生成了一个什么样的解构.一个react组件不光由若干个这些嵌套的虚拟dom对象组成,还包括各种生命周 ...
随机推荐
- Hibernate 的核心配置文件
核心配置文件 <!-- SessionFactory,相当于之前学习连接池配置 --> <session-factory> <!-- 1 基本4项 --> < ...
- Ubuntu18.04 安装 oh-my-zsh
目录 Ubuntu18.04 安装 oh-my-zsh 目录 安装zsh 安装curl 安装oh-my-zsh 使用zsh替换bash 修改终端主题和配色 修改终端配置 隐藏用户和主机名 效果图 Ub ...
- 【Codeforces Round #505 (rated, Div. 1 + Div. 2, based on VK Cup 2018 Final) B】Weakened Common Divisor
[链接] 我是链接,点我呀:) [题意] 给你n个数对(ai,bi). 让你求一个大于1的数字x 使得对于任意的i x|a[i] 或者 x|b[i] [题解] 求出第一个数对的两个数他们有哪些质因子. ...
- DOM中元素节点,属性节点,文本节点的理解
节点信息 每个节点都拥有包含着关于节点某些信息的属性.这些属性是: nodeName(节点名称) nodeValue(节点值) nodeType(节点类型) nodeType nodeType 属性可 ...
- hdu 2435dinic算法模板+最小割性质
hdu2435最大流最小割 2014-03-22 我来说两句 来源:hdu2435最大流最小割 收藏 我要投稿 2435 There is a war 题意: 给你一个有向图,其中可以有一条边是无敌的 ...
- 杭电(hdu)ACM 4548 美素数
美素数 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others) Total Submis ...
- 6.设置ListView的Item的高度无效
问题: 设置ListView的Item的高度无效. 解决方式: 设置ListView的Item的minHeight属性.
- 升级Xcode 导致插件失效的解决的方法
我们在升级xcode的情况下,我们的一些第三方插件就会失效. 比方cocoapods,等比較重要的三方插件, 解决这个问题例如以下: 进入插件文件夹:~/Library/Application Sup ...
- YunOS曙光初现----看好阿里云OS----阿冬专栏!!
阿里云os - YunOS 阿里云OS(YunOS)是阿里巴巴集团的智能手机操作系统,依托于阿里巴巴集团电子商务领域积累的经验和强大的云计算平台,基于LINUX开发. 魅族4阿里yun OS版已上市. ...
- 0x22 迭代加深
poj2248 真是个新套路.还有套路剪枝...大到小和判重 #include<cstdio> #include<iostream> #include<cstring&g ...