React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定。修改数据的同时,可以实现View的刷新。这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便。这个特性则要归功于setState()方法。React中利用队列机制来管理state,避免了很多重复的View刷新。下面我们来从源码角度探寻下setState机制。
1 还是先声明一个组件,从最开始一步步来寻源;
所以主要来看是否传入了updater参数,也就是说何时进行 new 组件;具体的updater参数是怎么传递进来的,以及是那个对象,参见
react源码分析系列文章下面的react中context updater到底是如何传递的
这里直接说结果,updater对象其实就是ReactUpdateQueue.js 中暴漏出的ReactUpdateQueue对象;
2 既然找到了setState之后执行的动作,我们在一步步深入进去
ReactComponent.prototype.setState = function (partialState,'setState');
}
};
ReactUpdateQueue.js
ReactUpdates.enqueueUpdate(internalInstance);
};
function getInternalInstanceReadyForUpdate(publicInstance,callerName) {
//在ReactCompositeComponent.js中有这样一行代码,这就是其来源;
// Store a reference from the instance back to the internal representation
//ReactInstanceMap.set(inst,this);
var internalInstance = ReactInstanceMap.get(publicInstance);
//返回的是在ReactCompositeComponent.js中construct函数返回的对象;ReactInstance实例对象并不是简单的new 我们写的组件的实例对象,而是经过instantiateReactComponent.js中ReactCompositeComponentWrapper函数包装的对象;详见 创建React组件方式以及源码分析.md
return internalInstance;
};
var ReactUpdateQueue = {
//。。。。省略其他代码
enqueueCallback: function (publicInstance,callerName) {
ReactUpdateQueue.validateCallback(callback,callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (!internalInstance) {
return null;
}
//这里将callback放入组件实例的_pendingCallbacks数组中;
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
// TODO: The callback here is ignored when setState is called from
// componentWillMount. Either fix it or disallow doing so completely in
// favor of getInitialState. Alternatively,we can disallow
// componentWillMount during server-side rendering.
enqueueUpdate(internalInstance);
},enqueueSetState: function (publicInstance,partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance,'setState');
if (!internalInstance) {
return;
}
//这里,初始化queue变量,同时初始化 internalInstance._pendingStateQueue = [ ] ;
//对于 || 的短路运算还是要多梳理下
//queue数组(模拟队列)中存放着setState放进来的对象;
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
//这里将partialState放入queue数组中,也就是internalInstance._pendingStateQueue 数组中,此时,每次setState的partialState,都放进了React组件实例对象上的_pendingStateQueue属性中,成为一个数组;
queue.push(partialState);
enqueueUpdate(internalInstance);
},};
module.exports = ReactUpdateQueue;
可以看到enqueueSetState enqueueCallback 最后都会执行enqueueUpdate;
ReactUpdates.js
if (!batchingStrategy.isBatchingUpdates) {
//batchedUpdates就是ReactDefaultBatchingStrategy.js中声明的
batchingStrategy.batchedUpdates(enqueueUpdate,component);
return;
}
//第二次执行setState的时候,进入不了if语句,将组件放入dirtyComponents
//如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
};
//enqueueUpdate包含了React避免重复render的逻辑。mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。之后React以事务的方式处理组件update,事务处理完后会调用wrapper.close(),而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES这个wrapper,故最终会调用RESET_BATCHED_UPDATES.close(),它最终会将isBatchingUpdates设置为false。
ReactDefaultBatchingStrategy.js
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES,RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype,Transaction,{
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});
//这个transition就是下面ReactDefaultBatchingStrategy对象中使用的transaction变量
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,/**
- Call the provided function in a context within which calls to
setState
- and friends are batched such that components aren't updated unnecessarily.
*/
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) {
return callback(a,e);
} else {
//transition在上面已经声明; // 以事务的方式处理updates,后面详细分析transaction
return transaction.perform(callback,null,e);
}
}
};
module.exports = ReactDefaultBatchingStrategy;
接下来我们看下React中的事物处理机制到底是如何运行的;
Transaction.js
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},closeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this,initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
};
module.exports = TransactionImpl
//3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
接着会执行ReactUpdates.js中的flushBatchedUpdates方法
ReactUpdates.js中
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
//这里执行runBatchedUpdates函数;
transaction.perform(runBatchedUpdates,transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
// Duck type TopLevelWrapper. This is probably always true.
if (component._currentElement.type.isReactTopLevelWrapper) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
//这里才是真正的开始更新组件
ReactReconciler.performUpdateIfNecessary(component,transaction.reconcileTransaction,updateBatchNumber);
if (markerName) {
console.timeEnd(markerName);
}
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j],component.getPublicInstance());
}
}
}
}
ReactReconciler.js中
ReactCompositeComponent.js
updateComponent: function (transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext) {
var inst = this._instance;
var willReceive = false;
var nextContext;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;
// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
if (process.env.NODE_ENV !== 'production') {
measureLifeCyclePerf(function () {
return inst.componentWillReceiveProps(nextProps,nextContext);
},this._debugID,'componentWillReceiveProps');
} else {
inst.componentWillReceiveProps(nextProps,nextContext);
}
}
//这里可以知道为什么setState可以接受函数,主要就是_processPendingState函数;
//这里仅仅是将每次setState放入到_pendingStateQueue队列中的值,合并到nextState,并没有真正的更新state的值;真正更新组件的state的值是在下面;
var nextState = this._processPendingState(nextProps,nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
if (process.env.NODE_ENV !== 'production') {
shouldUpdate = measureLifeCyclePerf(function () {
return inst.shouldComponentUpdate(nextProps,nextState,nextContext);
},'shouldComponentUpdate');
} else {
shouldUpdate = inst.shouldComponentUpdate(nextProps,nextContext);
}
} else {
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps,nextProps) || !shallowEqual(inst.state,nextState);
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set this.props
,this.state
and this.context
.
this._performComponentUpdate(nextParentElement,nextProps,nextContext,nextUnmaskedContext);
} else {
// If it's determined that a component should not update,we still want
// to set props and state but we shortcut the rest of the update.
//诺:在这里更新组件的state. props 等值;
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},_processPendingState: function (props,context) {
var inst = this._instance;
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];
//如果是setState的参数是一个函数,那么该函数接受三个参数,分别是state props context
_assign(nextState,typeof partial === 'function' ? partial.call(inst,props,context) : partial);
}
return nextState;
},
this.state的更新会在_processPendingState执行完执行。所以两次setState取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是React为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。来看个小例子直观感受下
setState流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程如下
- enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
- 如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
- batchedUpdates发起一次transaction.perform()事务
- 开始执行事务初始化,运行,结束三个阶段
- FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks,也就是setState中设置的callback。