節(jié)選自 React 源碼筆記(尚未研究完),由于知識(shí)粒度較為合適顷蟆,所以單獨(dú)先貼出來。
顧名思義腐魂,批量更新帐偎,可以避免短期內(nèi)的多次渲染,攢為一次性更新蛔屹。
在后面提供的 demo 中的 handleClick
中有三種方式調(diào)用 this.countNumber()
:
- 事件處理函數(shù)自帶batchedUpdates
- setTimeout中沒有batchedUpdates
- 主動(dòng)batchedUpdates
第1種:
批量更新削樊,會(huì)打印 0 0 0,然后按鈕文本顯示為3兔毒。每次 setState
雖然都會(huì)經(jīng)過 enqueueUpdate
(創(chuàng)建update 并加入隊(duì)列)-> scheduleWork
(尋找對(duì)應(yīng)的 FiberRoot 節(jié)點(diǎn))-> requestWork
(把 FiberRoot 加入到調(diào)度隊(duì)列),可惜上下文變量 isBatchingUpdates
在外部某個(gè)地方被標(biāo)記為了 true
漫贞,因此本次 setState
一路走來,尚未到達(dá)接下來的 performSyncWork
或者 scheduleCakkbackWithExpirationTime
就開始一路 return 出棧:
isBatchingUpdates
變量在早前的調(diào)用棧中(我們?yōu)?onClick 綁定的事件處理函數(shù)會(huì)被 react 包裹多層)育叁,被標(biāo)記為了 true
迅脐,然后 fn(a, b)
內(nèi)部經(jīng)過了3次 setState
系列操作,然后 finally 中 isBatchingUpdates
恢復(fù)為之前的 false豪嗽,此時(shí)執(zhí)行同步更新工作 performSyncWork
:
第2種:
在 handleClick
中使用 setTimeout
將 this.countNumber
包裹了一層 setTimeout(() => { this.countNumber()}, 0)
谴蔑,同樣要調(diào)用 handleClick
也是先經(jīng)過 interactiveUpdates$1
上下文豌骏,也會(huì)執(zhí)行 setTimeout
,然后 fn(a, b)
就執(zhí)行完了隐锭,因?yàn)樽罱K是瀏覽器來調(diào)用 setTimeout
的回調(diào) 然后執(zhí)行里面的 this.countNumber
窃躲,而對(duì)于 interactiveUpdates$1
來說繼續(xù)把自己的 performSyncWork
執(zhí)行完,就算結(jié)束了钦睡。顯然不管 performSyncWork
做了什么同步更新框舔,我們的 ?setState
目前為止都還沒得到執(zhí)行。然后等到 ?setTimeout
的回調(diào)函數(shù)等到空閑被執(zhí)行的時(shí)候赎婚,才會(huì)執(zhí)行 ?setState
刘绣,此時(shí)沒有了批量更新之上下文,所以每個(gè) ?setState
都會(huì)單獨(dú)執(zhí)行一遍 ?requestWork
中的 ?performSyncWork
直到渲染結(jié)束挣输,且不會(huì)被打斷纬凤,3次 ?setState
就會(huì)整個(gè)更新渲染 3 遍(這樣性能不好,所以一般不會(huì)這樣寫 react)撩嚼。
什么叫不會(huì)被打斷的同步更新渲染停士?看一下 demo 中的輸出,每次都同步打印出了最新的 button dom 的 ?innerText
完丽。
第3種:
已經(jīng)可以猜到恋技,無非就是因?yàn)槭褂??setTimeout
而“錯(cuò)過了”第一次的批量更新上下文,那等到 ?setTimeout
的回調(diào)執(zhí)行的時(shí)候逻族,專門再創(chuàng)建一個(gè)批量更新上下文即可:
總結(jié):
setState 是同步還是異步蜻底?
setState 方法本身是被同步調(diào)用,但并不代表 react 的 state 就會(huì)被立馬同步地更新聘鳞,而是要根據(jù)當(dāng)前執(zhí)行上下文來判斷薄辅。
如果處于批量更新的情況下,state 不會(huì)立馬被更新抠璃,而是批量更新站楚。
如果非批量更新的情況下,那么就“有可能”是立馬同步更新的搏嗡。為什么不是“一定”窿春?因?yàn)槿绻?React 開啟了 Concurrent Mode,非批量更新會(huì)進(jìn)入之前介紹過的異步調(diào)度中(時(shí)間分片)采盒。
批量更新演示 demo:
import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'
export default class BatchedDemo extends React.Component {
state = {
number: 0,
}
handleClick = () => {
// 事件處理函數(shù)自帶batchedUpdates
this.countNumber()
// setTimeout中沒有batchedUpdates
// setTimeout(() => {
// this.countNumber()
// }, 0)
// 主動(dòng)batchedUpdates
// setTimeout(() => {
// batchedUpdates(() => this.countNumber())
// }, 0)
}
countNumber() {
const button = document.getElementById('myButton')
const num = this.state.number
this.setState({
number: num + 1,
})
console.log(this.state.number)
console.log(button.innerText)
this.setState({
number: num + 2,
})
console.log(this.state.number)
console.log(button.innerText)
this.setState({
number: num + 3,
})
console.log(this.state.number)
console.log(button.innerText)
}
render() {
return <button id="myButton" onClick={this.handleClick}>Num: {this.state.number}</button>
}
}