setState 對於大多數人來講並不陌生,能夠說是 react 中高頻出現的,可是有些時候可以當即拿到結果,有些時候卻不能。那麼 setState 究竟是同步仍是異步的呢?javascript
constructor(props) {
super(props);
this.state = {
number: 1,
};
}
componentDidMount() {
this.setState({ number: 17 });
console.log(this.state.number); // 1
this.setState({ number: 27 });
console.log(this.state.number); // 1
}
複製代碼
咱們發現 2 次打印結果都是 1,從表象上看,像是異步的操做, 但其實只是異步的表現形式,每次調用 setState 都會觸發更新,出於性能考慮,React 會把多個 setState() 調用合併成一個調用,減小從新 render 的次數。java
setTimeout(() => {
this.setState({ number: 17 });
console.log(this.state.number); // 17
}, 0);
複製代碼
這時的打印結果又是 17 了,看起來像是同步的操做。react
setState 在面試題中也頻繁出現:面試
state = {
number: 1,
};
increment = () => {
console.log("increment setState前的number", this.state.number);
this.setState({
number: this.state.number + 1,
});
console.log("increment setState後的number", this.state.number);
};
triple = () => {
console.log("triple setState前的number", this.state.number);
this.setState({
number: this.state.number + 1,
});
this.setState({
number: this.state.number + 1,
});
this.setState({
number: this.state.number + 1,
});
console.log("triple setState後的number", this.state.number);
};
reduce = () => {
setTimeout(() => {
console.log("reduce setState前的number", this.state.number);
this.setState({
number: this.state.number - 1,
});
console.log("reduce setState後的number", this.state.number);
}, 0);
};
複製代碼
render(){
return <div>
<button onClick={this.increment}>點我增長</button>
<button onClick={this.triple}>點我增長三倍</button>
<button onClick={this.reduce}>點我減小</button>
</div>
}
複製代碼
這 3 個按鈕依次點擊的打印結果是什麼呢?算法
打印結果依次爲:性能優化
increment setState前的number 1
increment setState後的number 1
triple setState前的number 2
triple setState後的number 2
reduce setState前的number 3
reduce setState後的number 2
複製代碼
你答對了嗎?(若是答錯的話,面試就掛啦)markdown
那麼 setState 究竟是同步仍是異步的呢?接下來咱們來看一下調用 setState 以後發生了什麼。異步
在代碼中調用 setState 函數以後,React 會將傳入的參數對象與組件當前的狀態合併,而後觸發所謂的調和過程(Reconciliation)。通過調和過程,React 會以相對高效的方式根據新的狀態構建 React 元素樹而且着手從新渲染整個 UI 界面。在 React 獲得元素樹以後,React 會自動計算出新的樹與老樹的節點差別,而後根據差別對界面進行最小化重渲染。在差別計算算法中,React 可以相對精確地知道哪些位置發生了改變以及應該如何改變,這就保證了按需更新,而不是所有從新渲染。函數
setState 工做流爲:性能
isBatchingUpdates 是一個 react 全局惟一的變量,初始值是 false,意味着「當前並未進行任何批量更新操做」,每當 React 去執行更新動做時,會將 isBatchingUpdates 置爲 true,表示如今正處於批量更新過程當中。置爲 true 時,任何須要更新的組件都只能暫時進入 dirtyComponents 裏排隊等候下一次的批量更新。 isBatchingUpdates 這個變量,在 React 的生命週期函數以及合成事件執行前,已經被 React 悄悄修改成了 true,這時咱們所作的 setState 操做天然不會當即生效。當函數執行完畢後,事務的 close 方法會再把 isBatchingUpdates 改成 false。 由於 isBatchingUpdates 是在同步代碼中變化的,而 setTimeout 的邏輯是異步執行的。當 this.setState 調用真正發生的時候,isBatchingUpdates 早已經被重置爲了 false,這就使得當前場景下的 setState 具有了馬上發起同步更新的能力。
瞭解了 setState 的實現方式以後,咱們來簡單的模擬實現一個 setState,總結一下,主要是要實現 2 個功能:
調用 setState 以後首先執行了 enqueueSetState 方法
setState( stateChange ) {
enqueueSetState( stateChange, this );
}
複製代碼
爲了合併 setState,咱們須要一個隊列來保存每次 setState 的數據,而後在一段時間後,清空這個隊列並渲染組件。
const queue = [];
const renderQueue = [];
function enqueueSetState(stateChange, component) {
// 將新的 state 放進組件的狀態隊列裏
queue.push({
stateChange,
component,
});
// 若是renderQueue裏沒有當前組件,則添加到隊列中
if (!renderQueue.some((item) => item === component)) {
renderQueue.push(component);
}
// 根據 this 拿到對應的組件實例
var internalInstance = getInternalInstanceReadyForUpdate(
component,
"setState"
);
// 用 enqueueUpdate 來處理將要更新的實例對象
enqueueUpdate(internalInstance);
}
複製代碼
而後咱們來實現 enqueueUpdate 方法
enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
// 若當前沒有處於批量建立/更新組件的階段,則當即更新組件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 不然,先把組件塞入 dirtyComponents 隊列裏,讓它「再等等」
dirtyComponents.push(component);
}
複製代碼
setState( stateChange,callback ) {
enqueueSetState( stateChange,this );
if(callback) {
this.enqueueCallback(this, callback, 'setState')
}
}
複製代碼
那麼咱們如何同步的獲取到更新後的數據呢?在官方文檔中有所說明:
setState() 並不老是當即更新組件。它會批量推遲更新。這使得在調用 setState() 後當即讀取 this.state 成爲了隱患。爲了消除隱患,請使用 componentDidUpdate 或者 setState 的回調函數(setState(updater, callback)),這兩種方式均可以保證在應用更新後觸發。
this.setState({ number: 17 }, () => {
console.log(this.state.number); // 17
});
複製代碼
setState 自己並非異步,只是由於 react 的性能優化機制體現爲異步。在 react 的生命週期函數或者合成事件下爲異步,在 DOM 原生事件下以及 setTimeOut 爲同步。
這裏所說的同步異步,並非真正的同步異步,它仍是同步執行的。
這裏的異步指的是多個 state 會合併到一塊兒進行批量更新。
但願初學者不要被誤導。