轉自IMWeb社群,作者:黃qiong,原文連結
前言
學過react的人都知道,setState在react裡是一個很重要的方法,使用它可以更新我們資料的狀態,本篇文章從簡單使用到深入到setState的內部,全方位為你揭開setState的神祕面紗~
setState的使用注意事項
setState(updater, callback)
這個方法是用來告訴react元件資料有更新,有可能需要重新渲染。它是非同步的,react通常會集齊一批需要更新的元件,然後一次性更新來保證渲染的效能,所以這就給我們埋了一個坑:
那就是在使用setState
改變狀態之後,立刻通過this.state
去拿最新的狀態往往是拿不到的。
要點一
所以第一個使用要點就是:如果你需要基於最新的state做業務的話,可以在componentDidUpdate
或者setState
的回撥函式裡獲取。(注:官方推薦第一種做法)
// setState回撥函式
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
複製程式碼
要點二
設想有一個需求,需要在在onClick裡累加兩次,如下
onClick = () => {
this.setState({ index: this.state.index + 1 });
this.setState({ index: this.state.index + 1 });
}
複製程式碼
在react眼中,這個方法最終會變成
Object.assign(
previousState,
{index: state.index+ 1},
{index: state.index+ 1},
...
)
複製程式碼
由於後面的資料會覆蓋前面的更改,所以最終只加了一次.所以如果是下一個state依賴前一個state的話,推薦給setState傳function
onClick = () => {
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
}
複製程式碼
以上是使用setState的兩個注意事項,接下來我們來看看setState被呼叫之後,更新元件的過程,下面是一個簡單的流程圖。
下面來逐步的解析圖裡的流程。
一、setState
ReactBaseClassses.js
ReactComponent.prototype.setState = function (partialState, callback) {
// 將setState事務放進佇列中
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
複製程式碼
這裡的partialState可以傳object,也可以傳function,它會產生新的state以一種Object.assgine()
的方式跟舊的state進行合併。
二、enqueueSetState
enqueueSetState: function (publicInstance, partialState) {
// 獲取當前元件的instance
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 將要更新的state放入一個陣列裡
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 將要更新的component instance也放在一個佇列裡
enqueueUpdate(internalInstance);
}
複製程式碼
這段程式碼可以得知,enqueueSetState 做了兩件事: 1、將新的state放進陣列裡 2、用enqueueUpdate來處理將要更新的例項物件
三、enqueueUpdate
ReactUpdates.js
function enqueueUpdate(component) {
// 如果沒有處於批量建立/更新元件的階段,則處理update state事務
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正處於批量建立/更新元件的過程,將當前的元件放在dirtyComponents陣列中
dirtyComponents.push(component);
}
複製程式碼
由這段程式碼可以看到,當前如果正處於建立/更新元件的過程,就不會立刻去更新元件,而是先把當前的元件放在dirtyComponent裡,所以不是每一次的setState都會更新元件~。
這段程式碼就解釋了我們常常聽說的:setState是一個非同步的過程,它會集齊一批需要更新的元件然後一起更新。
而batchingStrategy 又是個什麼東西呢?
四、batchingStrategy
ReactDefaultBatchingStrategy.js
var ReactDefaultBatchingStrategy = {
// 用於標記當前是否出於批量更新
isBatchingUpdates: false,
// 當呼叫這個方法時,正式開始批量更新
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 如果當前事務正在更新過程在中,則呼叫callback,既enqueueUpdate
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 否則執行更新事務
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
複製程式碼
這裡注意兩點: 1、如果當前事務正在更新過程中,則使用enqueueUpdate
將當前元件放在dirtyComponent
裡。 2、如果當前不在更新過程的話,則執行更新事務。
五、transaction
/**
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
*/
複製程式碼
簡單說明一下transaction物件,它暴露了一個perform的方法,用來執行anyMethod,在anyMethod執行的前,需要先執行所有wrapper的initialize方法,在執行完後,要執行所有wrapper的close方法,就辣麼簡單。
在ReactDefaultBatchingStrategy.js,tranction 的 wrapper有兩個 FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES
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];
複製程式碼
可以看到,這兩個wrapper的initialize
都沒有做什麼事情,但是在callback執行完之後,RESET_BATCHED_UPDATES 的作用是將isBatchingUpdates置為false, FLUSH_BATCHED_UPDATES 的作用是執行flushBatchedUpdates,然後裡面會迴圈所有dirtyComponent,呼叫updateComponent來執行所有的生命週期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最後實現元件的更新。
以上即為setState的實現過程,最後還是用一個流程圖在做一個總結吧~
參考文件: