React原始碼分析5 — setState機制
1 概述
React作為一門前端框架,雖然只是focus在MVVM中的View部分,但還是實現了View和model的繫結。修改資料的同時,可以實現View的重新整理。這大大簡化了我們的邏輯,只用關心資料流的變化,同時減少了程式碼量,使得後期維護也更加方便。這個特性則要歸功於setState()方法。React中利用佇列機制來管理state,避免了很多重複的View重新整理。下面我們來從原始碼角度探尋下setState機制。
2 setState和replaceState
我們都知道setState是以修改和新增的方式改變state的,不會改變沒有涉及到的state。而replaceState則用新的state完全替換掉老state。比如
this.state = {
title: "example",
desc: "this is an example for react"
};
setState({
title: "new example"
});
console.log("setState: " + JSON.stringify(this.state)); // 1
replaceState({
title: "new example"
})
console.log("replaceState: " + JSON.stringify(this.state)); // 2
列印如下
setState: {"title" :"new example","desc":"this is an example for react"}
replaceState: {"title":"new example"}
可見,setState不會影響沒有涉及到的state,而replaceState則是完完全全的替換。下面讓我們進入原始碼來探尋究竟吧。
setState
setState方法入口如下
ReactComponent.prototype.setState = function (partialState, callback) {
// 將setState事務放入佇列中
this.updater.enqueueSetState(this , partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
取名為partialState,有部分state的含義,可見只是影響涉及到的state,不會傷及無辜。enqueueSetState是state佇列管理的入口方法,比較重要,我們之後再接著分析。
replaceState
replaceState: function (newState, callback) {
this.updater.enqueueReplaceState(this, newState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'replaceState');
}
},
replaceState中取名為newState,有完全替換的含義。同樣也是以佇列的形式來管理的。
3 enqueueSetState
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原始碼如下,解釋都在程式碼註釋中
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;
}
enqueueUpdate原始碼如下
function enqueueUpdate(component) {
ensureInjected();
// 如果不是正處於建立或更新元件階段,則處理update事務
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正在建立或更新元件,則暫且先不處理update,只是將元件放在dirtyComponents陣列中
dirtyComponents.push(component);
}
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: 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];
4 事務transaction
事務通過wrapper進行封裝。一個wrapper包含一對initialize和close方法。比如RESET_BATCHED_UPDATES
var RESET_BATCHED_UPDATES = {
// 初始化呼叫
initialize: emptyFunction,
// 事務執行完成,close時呼叫
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
transcation被包裝在wrapper中,比如
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
transaction是通過transaction.perform(callback, args…)方法進入的,它會先呼叫註冊好的wrapper中的initialize方法,然後執行perform方法中的callback,最後再執行close方法。擷取React程式碼中的一張圖如下
下面分析transaction.perform(callback, args…)
perform: function (method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
// 先執行所有wrapper中的initialize方法
this.initializeAll(0);
// 再執行perform方法傳入的callback
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// 最後執行wrapper中的close方法
try {
this.closeAll(0);
} catch (err) {}
} else {
// 最後執行wrapper中的close方法
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
// 遍歷所有註冊的wrapper
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
// 呼叫wrapper的initialize方法
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
closeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
// 遍歷所有wrapper
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
// 呼叫wrapper的close方法,如果有的話
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
5 runBatchedUpdates更新元件
前面分析到enqueueUpdate中呼叫transaction.perform(callback, args…)後,小夥伴們肯定會發現,callback還是enqueueUpdate方法啊,那豈不是死迴圈了?不是說好的setState會呼叫updateComponent,從而自動重新整理View的嗎?別急,我們還是要先從transaction事務說起。
我們的wrapper中註冊了兩個wrapper,如下
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
RESET_BATCHED_UPDATES用來管理isBatchingUpdates狀態,我們前面在分析setState是否立即生效時已經講解過了。那FLUSH_BATCHED_UPDATES用來幹嘛呢?
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var flushBatchedUpdates = function () {
// 迴圈遍歷處理完所有dirtyComponents
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
// close前執行完runBatchedUpdates方法,這是關鍵
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
FLUSH_BATCHED_UPDATES會在一個transaction的close階段執行runBatchedUpdates,從而執行update。
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);
}
},
最後驚喜的看到了receiveComponent和updateComponent吧。receiveComponent最後會呼叫updateComponent,而updateComponent中會執行React元件存在期的生命週期方法,如componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 從而完成元件更新的整套流程。
6 總結
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。