加入新團(tuán)隊(duì)后胀滚,團(tuán)隊(duì)項(xiàng)目使用了React Native。剛開(kāi)始接觸React Native茴迁,除了學(xué)習(xí)React Native的使用孔飒,更要了解React.js這個(gè)框架灌闺,才能更好的使用。而React框架中坏瞄,筆者一開(kāi)始就感覺(jué)奇妙的桂对,就是這個(gè)看似同步,表現(xiàn)卻不一定是同步的setState方法鸠匀〗缎保看了網(wǎng)上一些文章,結(jié)論似乎都是從某幾篇博客相互借鑒的結(jié)論缀棍,但里面筆者還是覺(jué)得有一些不太明白的地方宅此,幸虧React.js源碼是開(kāi)源的。順著源碼看下去睦柴,一些困惑的問(wèn)題終于有些眉目诽凌。
開(kāi)始之前毡熏,讀者可思考以下幾個(gè)問(wèn)題:
- setState是異步還是同步的
- 在setTimeout方法中調(diào)用setState的值為何馬上就能更新
- setState中傳入一個(gè)Function為何值馬上就能更新
- setState為何要如此設(shè)計(jì)
- setState的最佳實(shí)踐是什么
以下源碼基于我們團(tuán)隊(duì)在用的React 16.0.0版本坦敌,目前最新的React 16.4.0版本的類名和文件結(jié)構(gòu)均有很大變化,但設(shè)計(jì)思想應(yīng)該還是差不多的痢法,可供參考狱窘。
setState的入口
setState的最上層自然在ReactBaseClasses中。
//ReactBaseClasses.js
ReactComponent.prototype.setState = function(partialState, callback) {
// ...
//調(diào)用內(nèi)部updater
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
而這個(gè)updater财搁,在初始化時(shí)已經(jīng)告訴我們蘸炸,是實(shí)際使用時(shí)注入的。
//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是個(gè)什么類型搭儒。
//ReactCompositeComponet.js
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
) {
// ...
// 由Transaction獲取穷当,這個(gè)是ReactReconcileTransaction
var updateQueue = transaction.getUpdateQueue();
// ...
inst.updater = updateQueue;
// ...
}
//ReactReconcileTransaction.js
getUpdateQueue: function() {
return ReactUpdateQueue;
},
終于看到了具體enqueSetState方法的內(nèi)容。
//ReactUpdateQueue.js
enqueueSetState: function(
publicInstance,
partialState,
callback,
callerName,
) {
// ...
// 外部示例轉(zhuǎn)化為內(nèi)部實(shí)例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
// ...
// 將需要更新的state放入等待隊(duì)列中
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
// ...
// callback也一樣放入等待隊(duì)列中
if (callback !== null) {
// ...
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
}
enqueueUpdate(internalInstance);
},
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
而更新操作由ReactUpdates這個(gè)類負(fù)責(zé)淹禾。
//ReactUpdates.js
function enqueueUpdate(component) {
//...
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
//...
}
而這個(gè)isBatchingUpdates的判斷馁菜,就是代表是否在批量更新中。如果正在更新中铃岔,則整個(gè)組件放入dirtyComponents數(shù)組中汪疮,后面會(huì)講到。這里這個(gè)batchingStrategy毁习,其實(shí)就是ReactDefaultBatchingStrategy(外部注入的)智嚷。
//ReactDOMStackInjection.js
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
而這個(gè)類里的,則會(huì)讓掛起更新?tīng)顟B(tài)纺且,并調(diào)用transaction的perform盏道。
//ReactDefaultBatchingStrategy.js
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 正常情況不會(huì)走入if中
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
Transaction
這里簡(jiǎn)單解釋下事務(wù)(Transaction)的概念,先看源碼中對(duì)事務(wù)的一張解釋圖隆檀。
//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>
簡(jiǎn)單來(lái)說(shuō)摇天,事務(wù)相當(dāng)于對(duì)某個(gè)方法(anyMethod)執(zhí)行前和執(zhí)行后的多組鉤子的集合。
可以方便的在某個(gè)方法前后分別做一些事情恐仑,而且可以分wrapper定義泉坐,一個(gè)Wrapper一對(duì)鉤子。
具體來(lái)說(shuō)裳仆,可以在Wrapper里定義initialize和close方法腕让,initialize會(huì)在anyMethod執(zhí)行前執(zhí)行,close會(huì)在執(zhí)行后執(zhí)行歧斟。
更新策略里的Transaction
回到剛剛的batchedUpdates方法纯丸,里面那個(gè)transaction其實(shí)執(zhí)行前都是空方法,而callback是外界傳入的enqueueUpdate方法本身静袖,也就是說(shuō)觉鼻,執(zhí)行時(shí)會(huì)被isBatchingUpdates卡住進(jìn)入加入dirtyCompoments中。之后就會(huì)執(zhí)行close方法里面去改變isBatchingUpdates的值和執(zhí)行flushBatchedUpdates方法队橙。
//ReactDefaultBatchingStrategy.js
// 更新?tīng)顟B(tài)isBatchingUpdates的wrapper
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
// 真正更新?tīng)顟B(tài)的wrapper
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
// 兩個(gè)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();
而這個(gè)flushBatchedUpdates方法坠陈,按照dirtyComonents里的數(shù)量,每次執(zhí)行了一個(gè)transaction捐康。
//ReactUpdates.js
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
};
而這個(gè)transaction的執(zhí)行前后前后的鉤子如下仇矾。
//ReactUpdtes.js
// 開(kāi)始時(shí)同步dirComponents數(shù)量,結(jié)束時(shí)通過(guò)檢查是否在執(zhí)行中間runBatchedUpdates方法時(shí)還有新加入的component解总,有的話就重新執(zhí)行一遍
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;
},
});
所以真正更新方法應(yīng)該在runBatchedUpdates中贮匕。
//ReactUpdates.js
function runBatchedUpdates(transaction) {
// 排序,保證父組件比子組件先更新
dirtyComponents.sort(mountOrderComparator);
// ...
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
// 這里開(kāi)始進(jìn)入更新組件的方法
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction,
updateBatchNumber,
);
}
}
而ReactReconciler中的performUpateIfNecessary方法只是一個(gè)殼花枫。
performUpdateIfNecessary: function(
internalInstance,
transaction,
updateBatchNumber,
) {
// ...
internalInstance.performUpdateIfNecessary(transaction);
// ...
},
而真正的方法在ReactCompositeComponent中刻盐,如果等待隊(duì)列中有該更新的state掏膏,那么就調(diào)用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 {
// ...
}
},
這個(gè)方法判斷了做了一些判斷敦锌,而我們也看到了nextState的值才是最后被更新給state的值壤追。
//ReactCompositeComponent.js
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext,
) {
// 這個(gè)將排序隊(duì)列里的state合并到nextState
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
// 這里才正式更新state
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext,
);
} else {
// 這里才正式更新state
//...
inst.state = nextState;
//...
}
}
_performComponentUpdate: function(
nextElement,
nextProps,
nextState,
nextContext,
transaction,
unmaskedContext,
) {
var inst = this._instance;
var hasComponentDidUpdate = !!inst.componentDidUpdate;
var prevProps;
var prevState;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
}
// ...
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
// ...
},
這個(gè)方法也解釋了為什么傳入函數(shù)的state會(huì)更新。
_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傳入是函數(shù)供屉,那么接收的state是上輪更新過(guò)的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是同步的耶
而如果按照這個(gè)流程看完行冰,setState應(yīng)該是同步的呀?是哪里出了問(wèn)題呢伶丐。
別急悼做,還記得更新策略里面那個(gè)Transaction么。那里中間調(diào)用的callback是外層傳入的哗魂,也就說(shuō)有可能還有其它調(diào)用了batchedUpdates呢肛走。那么也就是說(shuō),中間的callback录别,并不止setState會(huì)引起朽色。在代碼里搜索后發(fā)現(xiàn),果真還有幾處調(diào)用了batchedUpdates方法组题。
比如ReactMount的這兩個(gè)方法
//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中直接調(diào)用時(shí)葫男,ReactMount.js 中的_renderNewRootComponent 方法已經(jīng)調(diào)用了,也就是說(shuō)崔列,整個(gè)將 React 組件渲染到 DOM 中的過(guò)程就處于一個(gè)大的 Transaction 中梢褐,而其中的callback沒(méi)有馬上被執(zhí)行,那么自然state沒(méi)有被馬上更新赵讯。
setState為什么這么設(shè)計(jì)
在react中盈咳,state代表UI的狀態(tài),也就是UI由state改變而改變边翼,也就是UI=function(state)鱼响。筆者覺(jué)得,這體現(xiàn)了一種響應(yīng)式的思想组底,而響應(yīng)式與命令式的不同丈积,在于命令式著重看如何命令的過(guò)程,而響應(yīng)式看中數(shù)據(jù)變化如何輸出斤寇。而React中對(duì)Rerender做出的努力桶癣,對(duì)渲染的優(yōu)化拥褂,響應(yīng)式的setState設(shè)計(jì)娘锁,其實(shí)也是其中搭配而不可少的一環(huán)。
最后
最前面的問(wèn)題饺鹃,相信每個(gè)人都有自己的答案莫秆,我這里給出我自己的理解间雀。
Q:setState是異步還是同步的?
A:同步的镊屎,但有時(shí)候是異步的表現(xiàn)惹挟。
Q:在setTimeout方法中調(diào)用setState的值為何馬上就能更新?
A:因?yàn)楸旧砭褪峭降姆觳担矝](méi)有別的因素阻塞连锯。
Q:setState中傳入一個(gè)Function為何值馬上就能更新?
A:源碼中的策略用狱。
Q:setState為何要如此設(shè)計(jì)运怖?
A:為了以響應(yīng)式的方式改變UI。
Q:setState的最佳實(shí)踐是什么夏伊?
A:以響應(yīng)式的思路使用摇展。