React.js源碼解析setState流程
setState
setState() 將對組件 state 的更改排入隊列批量推遲更新,并通知 React 需要使用更新后的 state 重新渲染此組件及其子組件。其實setState實際上不是異步,只是代碼執(zhí)行順序不同,有了異步的感覺。
使用方法 setState(stateChange | updater [, callback])
- stateChange - 作為被傳入的對象,將被淺層合并到新的 state 中
- updater -
(state, props) => stateChange,返回基于 state 和 props 構(gòu)建的新對象,將被淺層合并到新的 state 中 - callback - 為可選的回調(diào)函數(shù)
使用 setState() 改變狀態(tài)之后,立刻通過this.state拿不到最新的狀態(tài)
可以使用 componentDidUpdate() 或者 setState(updater, callback) 中的回調(diào)函數(shù) callback 保證在應用更新后觸發(fā),通常建議使用 componentDidUpdate()
多次setState()函數(shù)調(diào)用產(chǎn)生的效果會合并
為了更好的感知性能,React 會在同一周期內(nèi)會對多個 setState() 進行批處理。通過觸發(fā)一次組件的更新來引發(fā)回流。后調(diào)用的 setState() 將覆蓋同一周期內(nèi)先調(diào)用 setState() 的值。所以如果是下一個 state 依賴前一個 state 的話,推薦給 setState() 傳 function
onClick = () => {
this.setState({ quantity: this.state.quantity + 1 });
this.setState({ quantity: this.state.quantity + 1 });
}
// react中,這個方法最終會變成
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
同步 異步更新
同步更新
- React 引發(fā)的事件處理(比如通過onClick引發(fā)的事件處理)
- React 生命周期函數(shù)
異步更新
- 繞過React通過 addEventListener 直接添加的事件處理函數(shù)
- 通過 setTimeout || setInterval 產(chǎn)生的異步調(diào)用
setState()被調(diào)用之后,源碼執(zhí)行棧
react 參照版本 15.6.0
1. setState()
源碼路徑 src/isomorphic/modern/class/ReactBaseClasses.js
React組件繼承自React.Component,而setState是React.Component的方法,因此對于組件來講setState屬于其原型方法
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
}
2. enqueueSetState(); enqueueCallback()
源碼路徑 src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
這個文件導出了一個 ReactUpdateQueue 對象(React更新隊列)
enqueueSetState: function(publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState',
);
if (!internalInstance) {
return;
}
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
enqueueCallback: function(publicInstance, callback, callerName) {
ReactUpdateQueue.validateCallback(callback, callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (!internalInstance) {
return null;
}
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
enqueueUpdate(internalInstance);
}
3. enqueueUpdate()
源碼路徑 src/renderers/shared/stack/reconciler/ReactUpdates.js
如果處于批量更新模式,也就是 isBatchingUpdates 為 true 時,不進行state的更新操作,而是將需要更新的 component 添加到 dirtyComponents 數(shù)組中。
如果不處于批量更新模式,對所有隊列中的更新執(zhí)行 batchedUpdates 方法。
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
4. batchedUpdates()
源碼路徑 src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
如果 isBatchingUpdates 為 true,當前正處于更新事務狀態(tài)中,則將 Component 存入 dirtyComponent 中, 否則調(diào)用 batchedUpdates 處理,發(fā)起一個 transaction.perform()。
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
5. transaction initialize and close
源碼路徑 src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
Transaction 中注冊了兩個 wrapper,RESET_BATCHED_UPDATES 和 FLUSH_BATCHED_UPDATES。
initialize 階段,兩個 wrapper 都是空方法,什么都不做。
close 階段,RESET_BATCHED_UPDATES 將 isBatchingUpdates 設置為false;FLUSH_BATCHED_UPDATES 運行 flushBatchedUpdates 執(zhí)行update。
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
6. 渲染更新
ReactUpdates.flushBatchedUpdates() - ReactUpdates.runBatchedUpdates() - ReactCompositeComponent.performUpdateIfNecessary() - receiveComponent() + updateComponent()
runBatchedUpdates循環(huán)遍歷dirtyComponents數(shù)組,主要干兩件事:
- 首先執(zhí)行 performUpdateIfNecessary 來刷新組件的 view
- 執(zhí)行之前阻塞的 callback
receiveComponent 最后會調(diào)用 updateComponent
updateComponent 中會執(zhí)行 React 組件存在期的生命周期方法,如 componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 從而完成組件更新的整套流程
在shouldComponentUpdate之前,執(zhí)行了_processPendingState方法,該方法主要對state進行處理:
- 如果更新隊列為null,那么返回原來的state;
- 如果更新隊列有一個更新,那么返回更新值;
- 如果更新隊列有多個更新,那么通過for循環(huán)將它們合并;
在一個生命周期內(nèi),在componentShouldUpdate執(zhí)行之前,所有的state變化都會被合并,最后統(tǒng)一處理。
flushBatchedUpdates(); runBatchedUpdates() 源碼路徑 src/renderers/shared/stack/reconciler/ReactUpdates.js
var flushBatchedUpdates = function() {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, 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++) {
// dirtyComponents中取出一個component
var component = dirtyComponents[i];
// 取出dirtyComponent中的未執(zhí)行的callback
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
if (component._currentElement.type.isReactTopLevelWrapper) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
// 執(zhí)行updateComponent
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction,
updateBatchNumber,
);
// 執(zhí)行dirtyComponent中之前未執(zhí)行的callback
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(
callbacks[j],
component.getPublicInstance(),
);
}
}
}
}
performUpdateIfNecessary() 源碼路徑 src/renderers/shared/stack/reconciler/ReactReconciler.js
performUpdateIfNecessary: function(
internalInstance,
transaction,
updateBatchNumber,
) {
internalInstance.performUpdateIfNecessary(transaction);
},
};
performUpdateIfNecessary() 源碼路徑 src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(
this,
this._pendingElement,
transaction,
this._context,
);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(
transaction,
this._currentElement,
this._currentElement,
this._context,
this._context,
);
} else {
this._updateBatchNumber = null;
}
},
updateComponent() 源碼路徑 src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
事務概念
簡單地說,一個 Transaction 就是將需要執(zhí)行的 anyMethod 使用 wrapper 封裝起來,在通過 Transaction 提供的 perform 方法執(zhí)行,而在 perform 之前,先執(zhí)行所有 wrapper 中的 initialize 方法,perform 之后再執(zhí)行所有 wrapper 中的 close 方法。一組 initialize 及 close 方法稱為一個 wrapper,Transaction 支持多個 wrapper 疊加。

React 中的 Transaction 提供了一個 Mixin 方便其他模塊實現(xiàn)自己需要的事務。要實現(xiàn)自己的事務,需要額外實現(xiàn)一個抽象的 getTransactionWrappers() 接口,這個接口是 Transaction 用來獲取所有 wrapper 的 initialize 和 close 方法,因此需要返回一個數(shù)組對象,每個對象分別有 key 為 initialize 和 close 的方法。
var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');
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];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
setState 流程
setState 流程還是很復雜的,設計也很精巧,避免了重復無謂的刷新組件,React大量運用了注入機制,這樣每次注入的都是同一個實例化對象,防止多次實例化
- enqueueSetState 將 state 放入隊列中,并調(diào)用 enqueueUpdate 處理要更新的 Component
- 如果組件當前正處于 update 事務中,則先將 Component 存入 dirtyComponent 中。否則調(diào)用 batchedUpdates 處理
- batchedUpdates 發(fā)起一次 transaction.perform() 事務
開始執(zhí)行事務初始化,運行,結(jié)束三個階段
- 初始化:事務初始化階段沒有注冊方法,故無方法要執(zhí)行
- 運行:執(zhí)行 setSate 時傳入的 callback 方法,一般不會傳 callback 參數(shù)
- 結(jié)束:執(zhí)行
RESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATES這兩個 wrapper 中的 close 方法
FLUSH_BATCHED_UPDATES 在 close 階段,flushBatchedUpdates 方法會循環(huán)遍歷所有的 dirtyComponents ,調(diào)用 updateComponent 刷新組件,并執(zhí)行它的 pendingCallbacks , 也就是 setState 中設置的 callback
組件掛載后,setState一般是通過DOM交互事件觸發(fā),如 click
- 點擊button按鈕
- ReactEventListener 會觸發(fā) dispatchEvent方法
- dispatchEvent 調(diào)用 ReactUpdates.batchedUpdates
- 進入事務,init 為空, anyMethod 為
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
handleTopLevelImpl 是在這邊調(diào)用DOM事件對應的回調(diào)方法
然后是setState()
- 將state的變化和對應的回調(diào)函數(shù)放置到 _pendingStateQueue ,和 _pendingCallback 中
- 把需要更新的組件放到 dirtyComponents 序列中
- 執(zhí)行 perform()
- 執(zhí)行 close 渲染更新
dispatchEvent: function(topLevelType, nativeEvent) {
if (!ReactEventListener._enabled) {
return;
}
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
topLevelType,
nativeEvent,
);
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}以上就是React.js源碼解析setState流程的詳細內(nèi)容,更多關于React setState流程的資料請關注腳本之家其它相關文章!
相關文章
React不使用requestIdleCallback實現(xiàn)調(diào)度原理解析
這篇文章主要為大家介紹了React不使用requestIdleCallback實現(xiàn)調(diào)度原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
React中使用TS完成父組件調(diào)用子組件的操作方法
由于在項目開發(fā)過程中,我們往往時需要調(diào)用子組件中的方法,這篇文章主要介紹了React中使用TS完成父組件調(diào)用子組件,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07

