加入新团队后,团队项目使用了React Native。刚开始接触React Native,除了学习React Native的使用,更要了解React.js这个框架,才能更好的使用。而React框架中,笔者一开始就感受奇妙的,就是这个看似同步,表现却不必定是同步的setState方法。看了网上一些文章,结论彷佛都是从某几篇博客相互借鉴的结论,但里面笔者仍是以为有一些不太明白的地方,幸好React.js源码是开源的。顺着源码看下去,一些困惑的问题终于有些眉目。javascript
开始以前,读者可思考如下几个问题:java
如下源码基于咱们团队在用的React 16.0.0版本,目前最新的React 16.4.0版本的类名和文件结构均有很大变化,但设计思想应该仍是差很少的,可供参考。react
setState的最上层天然在ReactBaseClasses中。git
//ReactBaseClasses.js
ReactComponent.prototype.setState = function(partialState, callback) {
// ...
//调用内部updater
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
复制代码
而这个updater,在初始化时已经告诉咱们,是实际使用时注入的。github
//ReactBaseClasses.js
function ReactComponent(props, context, updater) {
// ...
// 真正的updater在renderer注入
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
复制代码
找到注入的地方,目的是找到updater是个什么类型。数组
//ReactCompositeComponet.js
mountComponent: function( transaction, hostParent, hostContainerInfo, context, ) {
// ...
// 由Transaction获取,这个是ReactReconcileTransaction
var updateQueue = transaction.getUpdateQueue();
// ...
inst.updater = updateQueue;
// ...
}
复制代码
//ReactReconcileTransaction.js
getUpdateQueue: function() {
return ReactUpdateQueue;
},
复制代码
终于看到了具体enqueSetState方法的内容。app
//ReactUpdateQueue.js
enqueueSetState: function( publicInstance, partialState, callback, callerName, ) {
// ...
// 外部示例转化为内部实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
// ...
// 将须要更新的state放入等待队列中
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
// ...
// callback也同样放入等待队列中
if (callback !== null) {
// ...
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
}
enqueueUpdate(internalInstance);
},
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
复制代码
而更新操做由ReactUpdates这个类负责。框架
//ReactUpdates.js
function enqueueUpdate(component) {
//...
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
//...
}
复制代码
而这个isBatchingUpdates的判断,就是表明是否在批量更新中。若是正在更新中,则整个组件放入dirtyComponents数组中,后面会讲到。这里这个batchingStrategy,其实就是ReactDefaultBatchingStrategy(外部注入的)。异步
//ReactDOMStackInjection.js
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
复制代码
而这个类里的,则会让挂起更新状态,并调用transaction的perform。函数
//ReactDefaultBatchingStrategy.js
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 正常状况不会走入if中
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
复制代码
这里简单解释下事务(Transaction)的概念,先看源码中对事务的一张解释图。
//Transaction.js
* <pre> * wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre>
复制代码
简单来讲,事务至关于对某个方法(anyMethod)执行前和执行后的多组钩子的集合。
能够方便的在某个方法先后分别作一些事情,并且能够分wrapper定义,一个Wrapper一对钩子。
具体来讲,能够在Wrapper里定义initialize和close方法,initialize会在anyMethod执行前执行,close会在执行后执行。
回到刚刚的batchedUpdates方法,里面那个transaction其实执行前都是空方法,而callback是外界传入的enqueueUpdate方法自己,也就是说,执行时会被isBatchingUpdates卡住进入加入dirtyCompoments中。以后就会执行close方法里面去改变isBatchingUpdates的值和执行flushBatchedUpdates方法。
//ReactDefaultBatchingStrategy.js
// 更新状态isBatchingUpdates的wrapper
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
// 真正更新状态的wrapper
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
// 两个wrapper
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
// 添加transaction的wrappers
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
var transaction = new ReactDefaultBatchingStrategyTransaction();
复制代码
而这个flushBatchedUpdates方法,按照dirtyComonents里的数量,每次执行了一个transaction。
//ReactUpdates.js
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
};
复制代码
而这个transaction的执行先后先后的钩子以下。
//ReactUpdtes.js
// 开始时同步dirComponents数量,结束时经过检查是否在执行中间runBatchedUpdates方法时还有新加入的component,有的话就从新执行一遍
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
},
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES];
//添加wrapper
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
复制代码
因此真正更新方法应该在runBatchedUpdates中。
//ReactUpdates.js
function runBatchedUpdates(transaction) {
// 排序,保证父组件比子组件先更新
dirtyComponents.sort(mountOrderComparator);
// ...
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
//这里开始进入更新组件的方法
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction,
updateBatchNumber,
);
}
}
复制代码
而ReactReconciler中的performUpateIfNecessary方法只是一个壳。
performUpdateIfNecessary: function( internalInstance, transaction, updateBatchNumber, ) {
// ...
internalInstance.performUpdateIfNecessary(transaction);
// ...
},
复制代码
而真正的方法在ReactCompositeComponent中,若是等待队列中有该更新的state,那么就调用updateComponent。
//ReactCompositeComponent.js
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement != null) {
// ...
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(
transaction,
this._currentElement,
this._currentElement,
this._context,
this._context,
);
} else {
// ...
}
},
复制代码
这个方法判断了作了一些判断,而咱们也看到了nextState的值才是最后被更新给state的值。
//ReactCompositeComponent.js
updateComponent: function( transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext, ) {
// 这个将排序队列里的state合并到nextState
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (shouldUpdate) {
//...
} else {
// 这里才正式更新state
//...
inst.state = nextState;
//...
}
}
复制代码
这个方法也解释了为何传入函数的state会更新。
_processPendingState: function(props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
//更新了就能够置空了
this._pendingStateQueue = null;
var nextState = replace ? queue[0] : inst.state;
var dontMutate = true;
for (var i = replace ? 1 : 0; i < queue.length; i++) {
//若是setState传入是函数,那么接收的state是上轮更新过的state
var partial = queue[i];
let partialState = typeof partial === 'function'
? partial.call(inst, nextState, props, context)
: partial;
if (partialState) {
if (dontMutate) {
dontMutate = false;
nextState = Object.assign({}, nextState, partialState);
} else {
Object.assign(nextState, partialState);
}
}
}
return nextState;
},
复制代码
而若是按照这个流程看完,setState应该是同步的呀?是哪里出了问题呢。
别急,还记得更新策略里面那个Transaction么。那里中间调用的callback是外层传入的,也就说有可能还有其它调用了batchedUpdates呢。那么也就是说,中间的callback,并不止setState会引发。在代码里搜索后发现,果然还有几处调用了batchedUpdates方法。
好比ReactMount的这两个方法
//ReactMount.js
_renderNewRootComponent: function( nextElement, container, shouldReuseMarkup, context, callback, ) {
// ...
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
// ...
},
unmountComponentAtNode: function(container) {
// ...
ReactUpdates.batchedUpdates(
unmountComponentFromNode,
prevComponent,
container,
);
return true;
// ...
},
复制代码
好比ReactDOMEventListener
//ReactDOMEventListener.js
dispatchEvent: function(topLevelType, nativeEvent) {
// ...
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
ReactGenericBatching.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
// ...
}
},
//ReactDOMStackInjection.js
ReactGenericBatching.injection.injectStackBatchedUpdates(
ReactUpdates.batchedUpdates,
);
复制代码
因此好比在componentDidMount中直接调用时,ReactMount.js 中的**_renderNewRootComponent** 方法已经调用了,也就是说,整个将 React 组件渲染到 DOM 中的过程就处于一个大的 Transaction 中,而其中的callback没有立刻被执行,那么天然state没有被立刻更新。
在react中,state表明UI的状态,也就是UI由state改变而改变,也就是UI=function(state)。笔者以为,这体现了一种响应式的思想,而响应式与命令式的不一样,在于命令式着重看如何命令的过程,而响应式看中数据变化如何输出。而React中对Rerender作出的努力,对渲染的优化,响应式的setState设计,其实也是其中搭配而不可少的一环。
最前面的问题,相信每一个人都有本身的答案,我这里给出我本身的理解。
Q:setState是异步仍是同步的?
A:同步的,但有时候是异步的表现。
Q:在setTimeout方法中调用setState的值为什么立刻就能更新?
A:由于自己就是同步的,也没有别的因素阻塞。
Q:setState中传入一个Function为什么值立刻就能更新?
A:源码中的策略。
Q:setState为什么要如此设计?
A:为了以响应式的方式改变UI。
Q:setState的最佳实践是什么?
A:以响应式的思路使用。