本文转载自:微信公众号 方凳雅集
写在前面的话react
setState是React很重要的模块, 社区中也有不少分析文章,大多强调setState是异步更新,但有些文章分析又说某些状况下是同步更新,那究竟是同步仍是异步呢,这篇文章仍是基于15.x进行的分析,16.x的分析等后面用机会再分享。算法
咱们看一下React官网对setState的说明:数组
官网也没说setState究竟是同步仍是异步,只是说React不保证setState以后可以当即拿到改变后的结果。浏览器
咱们先看一个经典例子微信
Demoapp
// demo.js
class Demo extends PureComponent {
state={dom
count: 0,
}
componentDidMount() {异步
console.log('pre state', this.state.count); this.setState({ count: this.state.count + 1 }); console.log('next state', this.state.count); //测试setTimeout setTimeout(() => { console.log('setTimeout pre state', this.state.count); this.setState({ count: this.state.count + 1 }); console.log('setTimeout next state', this.state.count); }, 0);
}async
onClick = (event) => {函数
// 测试合成函数中setState console.log(`${event.type} pre state`, this.state.count); this.setState({ count: this.state.count + 1 }); console.log(`${event.type} next state`, this.state.count);
}
render() {
return <button onClick={this.onClick}>count+1</button>
}
}
这里有三种方法调用setState:
在componentDidMount中直接调用setState;
在componentDidMount的setTimeout方法里调用setState;
在dom中绑定onClick(React的合成函数:抹平不一样浏览器和端的差别)直接调用setState;
从控制台打印出来的结果看,方法1和3直接调用setState是异步的,而方法2中setTimeout调用setState证实了同步,到底为何呢?这两种调用方式有什么区别嘛?接下来咱们从源码进行分析。
源码分析
setState入口函数
//ReactComponent.js
ReactComponent.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.') : invariant(false) : undefined;
if ("development" !== 'production') {
"development" !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : undefined;
}
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
//ReactUpdateQueue.js
enqueueSetState: function(publicInstance, partialState) {
// 根据 this.setState 中的 this 拿到内部实例, 也就是组件实例
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance, 'setState'
);
if (!internalInstance) {
return;
}
//取得组件实例的_pendingStateQueue
var queue =
internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
//将partial state存到_pendingStateQueue
queue.push(partialState);
//唤起enqueueUpdate
enqueueUpdate(internalInstance);
};
...
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
在setState函数中调用enqueueSetState, 拿到内部组件实例, 而后把要更新的partial state存到其_pendingStateQueue中,至此,setState调用方法执行结束,接下来是setState调用以后的动做。
调用 setState 后发生了什么?
setState调用以后执行方法enqueueUpdate
//ReactUpdates.js
function enqueueUpdate(component) {
//注入默认策略,开启ReactReconcileTransaction事务
ensureInjected();
// 若是没有开启batch(或当前batch已结束)就开启一次batch再执行, 这一般发生在异步回调中调用 setState
//batchingStrategy:批量更新策略,经过事务的方式实现state的批量更新
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component); return;
}
// 若是batch已经开启,则将该组件保存在 dirtyComponents 中存储更新
dirtyComponents.push(component);
}
上面demo对setState三次调用结果之因此不一样,应该是这里的判断逻辑致使的:
1和3的调用走的是isBatchingUpdates === true分支,没有执行更新操做;
2的setTimeout走的是isBatchingUpdates === false分支,执行更新;
isBatchingUpdates是事务batchingStrategy的一个标记,若是为true,把当前调用setState的组件放入dirtyComponents数组中,作存储处理,不会当即更新,若是为false,将enqueueUpdate做为参数传入batchedUpdates方法中,在batchedUpdates中执行更新操做。
但是事务batchingStrategy究竟是作什么的呢?batchedUpdates又作了什么处理?咱们看一下它的源码
//ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();// 实例化事务
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; // 开启一次batch ReactDefaultBatchingStrategy.isBatchingUpdates = true; if (alreadyBatchingUpdates) { callback(a, b, c, d, e); } else { // 启动事务, 将callback放进事务里执行 transaction.perform(callback, null, a, b, c, d, e); }
},
};
//说明:这里使用到了事务transaction,简单来讲,transaction就是将须要执行的方法使用 wrapper 封装起来,
//再经过事务提供的 perform 方法执行。而在 perform 以前,先执行全部 wrapper 中的 initialize 方法,
//执行完 perform 以后(即执行method 方法后)再执行全部的 close 方法。
//一组 initialize 及 close 方法称为一个 wrapper。事务支持多个 wrapper 叠加,嵌套,
//若是当前事务中引入了另外一个事务B,则会在事务B完成以后再回到当前事务中执行close方法。
(上面涉及到了事务,事务的具体分析有兴趣能够看文章最后)
ReactDefaultBatchingStrategy就是一个批量更新策略事务, isBatchingUpdates默认是false,而batchedUpdates方法被调用时才会将属性isBatchingUpdates设置为true,代表目前处于批量更新流中;但是上面demo中1和3执行到判断逻辑以前源码分析中没见到有batchedUpdates方法调用,那batchedUpdates何时被调用的呢?
全局搜索React中调用batchedUpdates的地方不少,分析后发现与更新流程相关的只有两个地方:
// ReactMount.js
_renderNewRootComponent: function(nextElement,container,shouldReuseMarkup,context) {
... // 实例化组件 var componentInstance = instantiateReactComponent(nextElement, null); //初始渲染是同步的,但在渲染期间发生的任何更新,在componentWillMount或componentDidMount中,将根据当前的批处理策略进行批处理 ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context );
...
},
// ReactEventListener.js
dispatchEvent: function (topLevelType, nativeEvent) {
... try { // 处理事件 ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping); } finally { TopLevelCallbackBookKeeping.release(bookKeeping); }
}
第一种状况,是在首次渲染组件时调用batchedUpdates,开启一次batch。由于组件在渲染的过程当中, 会依顺序调用各类生命周期函数, 开发者极可能在生命周期函数中(如componentWillMount或者componentDidMount)调用setState. 所以, 开启一次batch就是要存储更新(放入dirtyComponents), 而后在事务结束时批量更新. 这样以来, 在初始渲染流程中, 任何setState都会生效, 用户看到的始终是最新的状态
第二种状况,若是在组件上绑定了事件,在绑定事件中颇有可能触发setState,因此为了存储更新(dirtyComponents),须要开启批量更新策略。在回调函数被调用以前, React事件系统中的dispatchEvent函数负责事件的分发, 在dispatchEvent中启动了事务, 开启了一次batch, 随后调用了回调函数. 这样一来, 在事件的监听函数中调用的setState就会生效.
这里借用《深刻REACT技术栈》文章里的一个在componentDidMount中setState的调用栈图例
图例中代表,ReactDefaultBatchingStrategy.batchedUpdates在ReactMount.
renderNewRootComponent中被调用,依次倒推,最后发如今组件首次渲染时就会经过injectBatchingStrategy()方法注入ReactDefaultBatchingStrategy(这部分有兴趣能够看一下ReactDefaultInjection.js源码),而且在ReactMount.render中触发
renderNewRootComponent函数,调用batchedUpdates将isBatchingUpdates设置为了true,因此componentDidMount的执行都是在一个大的事务ReactDefaultBatchingStrategyTransaction中。
这就解释了在componentDidMount中调用setState并不会当即更新state,由于正处于一个这个大的事务中,isBatchingUpdates此时为true,因此只会放入dirtyComponents中等待稍后更新。
state何时批量更新呢?
追踪代码后我画了一个组件初次渲染和setState后简单的事务启动和执行的顺序:
从上面的图中能够看到,ReactDefaultBatchingStrategy就是一个批量更新策略事务,控制了批量策略的生命周期。看一下ReactDefaultBatchingStrategy源码分析一下事务中执行了什么:
// ReactDefaultBatchingStrategy.js
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
在事务的close阶段执行了flushBatchedUpdates函数,flushBatchedUpdates执行完以后再将ReactDefaultBatchingStrategy.isBatchingUpdates重置为false,表示此次batch更新结束。
flushBatchedUpdates函数启动ReactUpdatesFlushTransaction事务,这个事务开启了批量更新,执行runBatchedUpdates对dirtyComponents循环处理。
怎么批量更新的呢?
批量更新flushBatchedUpdates中,看一下源码
// ReactUpdates.js
var flushBatchedUpdates = function() {
// 开启批量更新
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) { var transaction = ReactUpdatesFlushTransaction.getPooled(); transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction); } // 批量处理callback if (asapEnqueued) { asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); }
}
};
flushBatchedUpdates开启事务ReactUpdatesFlushTransaction, 执行runBatchedUpdates,
// ReactUpdates.js
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
// 排序保证父组件优于子组件更新
dirtyComponents.sort(mountOrderComparator);
// 遍历dirtyComponents
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i]; var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; // 执行更新操做 ReactReconciler.performUpdateIfNecessary( component, transaction.reconcileTransaction ); // 存储callbacks if (callbacks) { for (var j = 0; j < callbacks.length; j++) { transaction.callbackQueue.enqueue( callbacks[j], component.getPublicInstance() ); } }
}
}
接下来就是ReactReconciler调用组件实例的performUpdateIfNecessary方法,这里只分析ReacrCompositeComponent实例,若是接收了props,就会调用receiveComponent方法,在该方法里调用updateComponent方法;若是有新的要更新的状态(_pendingStateQueue不为空)也会直接调用updateComponent来更新
// ReactCompositeComponent.js
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement != null) { ReactReconciler.receiveComponent( this, this._pendingElement || this._currentElement, transaction, this._context ); } // 待更新state队列不为空或者_pendingForceUpdate为true if (this._pendingStateQueue !== null || this._pendingForceUpdate) { this.updateComponent( transaction, this._currentElement, this._currentElement, this._context, this._context ); }
},
调用组件实例中的updateComponent,这块代码是组件更新机制的核心,负责管理生命周期中的componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate;
这段代码比较多,集中在ReactCompositeComponent.js文件中,
若是不想看源码能够直接看后面的代码流程图
//ReactCompositeComponent.js
updateComponent: function(
transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext
) {
var inst = this._instance; var nextContext = this._context === nextUnmaskedContext ? inst.context : this._processContext(nextUnmaskedContext); var nextProps; // Distinguish between a props update versus a simple state update if (prevParentElement === nextParentElement) { // Skip checking prop types again -- we don't read inst.props to avoid // warning for DOM component props in this upgrade nextProps = nextParentElement.props; } else { nextProps = this._processProps(nextParentElement.props); // 若是有接收新的props,执行componentWillReceiveProps 方法, if (inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); } } // 合并props var nextState = this._processPendingState(nextProps, nextContext); // 执行shouldComponentUpdate判断是否须要更新 var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext); ... // 若是须要更新执行_performComponentUpdate,不然只将当前的props和state保存下来,不作更新 if (shouldUpdate) { this._pendingForceUpdate = false; // Will set `this.props`, `this.state` and `this.context`. this._performComponentUpdate( nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext ); } else { this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; }
},
...
// 执行componentWillUpdate
_performComponentUpdate: function(
nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext
) {
var inst = this._instance; var hasComponentDidUpdate = Boolean(inst.componentDidUpdate); var prevProps; var prevState; var prevContext; if (hasComponentDidUpdate) { prevProps = inst.props; prevState = inst.state; prevContext = inst.context; } if (inst.componentWillUpdate) { inst.componentWillUpdate(nextProps, nextState, nextContext); } this._currentElement = nextElement; this._context = unmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; this._updateRenderedComponent(transaction, unmaskedContext); if (hasComponentDidUpdate) { transaction.getReactMountReady().enqueue( inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst ); }
}
// 执行unmountComponent,_instantiateReactComponent, mountComponent、render
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent; var prevRenderedElement = prevComponentInstance._currentElement; var nextRenderedElement = this._renderValidatedComponent(); // 若是prevRenderedElement, nextRenderedElement相等只执行receiveComponent if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { ReactReconciler.receiveComponent( prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context) ); } else { // prevRenderedElement, nextRenderedElement不相等,则执行旧组件的unmountComponent var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance); ReactReconciler.unmountComponent(prevComponentInstance); this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement); // 组件实例化_instantiateReactComponent this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); // 组件挂载 var nextMarkup = ReactReconciler.mountComponent( this._renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context) ); // 新组件替换旧组件 this._replaceNodeWithMarkup(oldNativeNode, nextMarkup); }
},
updateComponent流程图
demo扩展
上面分析了一个很经典的demo,下面看一下原生事件和async事件中setState调用后的表现。
绑定原生事件,调用setState
class Button extends PureComponent {
state={
count: 0, val: 0
}
componentDidMount() {
// 测试原生方法:手动绑定mousedown事件 console.log('mousedown pre state', this.state.count); ReactDOM.findDOMNode(this).addEventListener( "mousedown", this.onClick.bind(this) ); console.log('mousedown pre state', this.state.count);
}
onClick(event) {
console.log(`${event.type} pre state`, this.state.count); this.setState({ count: this.state.count + 1 }); console.log(`${event.type} next state`, this.state.count);
}
render() {
return <button onClick={this.onClick.bind(this)}>count+1</button>
}
}
控制台
async函数和sleep函数
class Button extends PureComponent {
state={
count: 0, val: 0
}
async componentDidMount() {
// 测试async函数中setState for(let i = 0; i < 1; i++){ console.log('sleep pre state', this.state.count); await sleep(0); this.setState({ count: this.state.count + 1 }); console.log('sleep next state', this.state.count); }
}
asyncClick = () => {
this.setState({ count: this.state.count + 1 });
}
async onClick(event) {
const type = event.type; console.log(`${type} pre state`, this.state.count); await this.asyncClick(); console.log(`${type} next state`, this.state.count);
}
render() {
return <button onClick={this.onClick.bind(this)}>count+1</button>
}
}
控制台
结论
setState在生命周期函数和合成函数中都是异步更新。
setState在steTimeout、原生事件和async函数中都是同步更新。
每次更新不表明都会触发render,若是render内容与newState有关联,则会触发,不然即使setState屡次也不会render
若是newState内容与render有依赖关系,就不建议同步更新,由于每次render都会完整的执行一次批量更新流程(只是dirtyComponets长度为1,stateQueue也只有该组件的newState),调用一次diff算法,这样会影响React性能。
若是没有必须同步渲染的理由,不建议使用同步,会影响react渲染性能
总结
React整个更新机制到处包含着事务,总的来讲,组件的更新机制依靠事务进行批量更新;
一次batch(批量)的生命周期就是从ReactDefaultBatchingStrategy事务perform以前(调用ReactUpdates.batchUpdates)到这个事务的最后一个close方法调用后结束;
事务启动后, 遇到 setState 则将 partial state 存到组件实例的_pendingStateQueue上, 而后将这个组件存到dirtyComponents 数组中, 等到 ReactDefaultBatchingStrategy事务结束时调用runBatchedUpdates批量更新全部组件;
组件的更新是递归的, 三种不一样类型的组件都有本身的updateComponent方法来决定本身的组件如何更新, 其中 ReactDOMComponent 会采用diff算法对比子元素中最小的变化, 再批量处理.
生命周期函数和合成函数中调用setState表现异步更新,是由于组件初始化和调用合成函数时都会触发ReactDefaultBatchingStrategy事务的batchUpdates方法,将批量更新标记设置为true,因此后面的setState都会存储到dirtyComponents中,执行批量更新以后再将标志设置为false;
setTimeout、原生事件和async函数中调用setState表现同步更新,是由于遇到这些函数时不会触发
ReactDefaultBatchingStrategy事务的batchUpdates方法,因此批量更新标记依旧时false,因此表现为同步。
补充:transaction事务介绍
React 的事务机制比较简单,包括三个阶段,initialize、perform和close,而且事务之间支持叠加。
事务提供了一个 mixin 方法供其余模块实现本身须要的事务。而要使用事务的模块,除了须要把 mixin 混入本身的事务实现中外,还要额外实现一个抽象的 getTransactionWrappers 接口。这个接口用来获取全部须要封装的前置方法(initialize)和收尾方法(close),
所以它须要返回一个数组的对象,每一个对象分别有 key 为 initialize 和 close 的方法。
这里看一个《深刻React技术栈》文章中的例子就比较好理解了
var Transaction = require('./Transaction');
// 咱们本身定义的事务
var MyTransaction = function() {
// ... };
Object.assign(MyTransaction.prototype, Transaction.Mixin, { getTransactionWrappers: function() {
return [{
initialize: function() { console.log('before method perform'); }, close: function() { console.log('after method perform'); }
}];
};
});
var transaction = new MyTransaction(); var testMethod = function() {
console.log('test'); }
transaction.perform(testMethod);
// 打印的结果以下:// before method perform // test// after method perform