setState()沒有立即生效這個(gè)問題虏冻,在做項(xiàng)目的時(shí)候不止一次遇到肤粱。每次遇到的時(shí)候,解決辦法都是現(xiàn)成的厨相,并沒有細(xì)細(xì)的深入探究為什么在調(diào)用setState的時(shí)候this.state在一定幾率下沒有立即生效的原因领曼。這次在做****的時(shí)候鸥鹉,又遇到了這個(gè)問題,決定詳細(xì)探究一下悯森,查一查資料宋舷,并做一個(gè)記錄。
首先瓢姻,看一下react官方對setState這個(gè)API的定義祝蝠。在react的官方文檔里這樣描述setState()這個(gè)API:
setState(object nextState[, function callback])
合并 nextState 和當(dāng)前 state。這是在事件處理函數(shù)中和請求回調(diào)函數(shù)中觸發(fā) UI 更新的主要方法幻碱。另外绎狭,也支持可選的回調(diào)函數(shù),該函數(shù)在 setState 執(zhí)行完畢并且組件重新渲染完成之后調(diào)用褥傍。
同時(shí)還提出了注意點(diǎn):
setState()不會(huì)立刻改變 this.state儡嘶,而是創(chuàng)建一個(gè)即將處理的 state 事務(wù)。在調(diào)用該方法之后獲取 this.state 的值可能會(huì)得到現(xiàn)有的值恍风,而不是最新設(shè)置的值蹦狂。不保證 setState() 調(diào)用的同步性偏塞,為了提升性能看政,可能會(huì)批量執(zhí)行 state 轉(zhuǎn)變和 DOM 渲染。
所以娶牌,一般情況下锦募,我們想要確保在state合并完成之后執(zhí)行方法摆屯,就會(huì)把需要執(zhí)行的內(nèi)容寫在回調(diào)函數(shù)內(nèi)。
當(dāng)然糠亩,不管怎樣還是要代碼說話,
class Example extends React.Component {
constructor(){
super();
this.state = {
val:0
};
}
componentDidMount() {
this.setState({val:this.state.val+1});
console.log(this.state.val);//第1次 0
this.setState({val:this.state.val+1});
console.log(this.state.val);//第2次 0
setTimeout(() => {
this.setState({val:this.state.val+1});
console.log(this.state.val);//第3次 2
this.setState({val:this.state.val+1});
console.log(this.state.val);//第4次 3
},0)
}
}
這四次輸出的val分別是0虐骑,0,2赎线,3廷没,是不是有點(diǎn)出乎意料。在第一次和第二次輸出的時(shí)候setState()到底干啥去了垂寥。
眾所周知腕柜,react的源碼的體量還是有點(diǎn)兒大的。但是為什么react的執(zhí)行會(huì)如此迅速矫废。根據(jù)核心開發(fā)者之一 Pete Hunt的闡述,其中有一個(gè)原因就是每一個(gè)組件都有其完整的生命周期砰蠢,這些生命周期方法保證所有對 DOM 的修改都是批量更新的(batch update)蓖扑。
為了搞明白setState之后發(fā)生了什么。我大致梳理了一下源碼(我看的源碼版本是0.14.7),將代碼的重點(diǎn)整理如下圖:
首先台舱,在我們調(diào)用 setState
方法后律杠,要明確的一點(diǎn)是 state
是不一定會(huì)立即更新的潭流。因?yàn)榇藭r(shí)會(huì)調(diào)用一個(gè) enqueueSetState
方法,傳入要更新的 state
柜去。如果存在 callback
的話灰嫉,也會(huì)調(diào)用 enqueueCallback
的方法。 在 enqueueSetState
方法中嗓奢,會(huì)將新的 state
暫時(shí)存入一個(gè)臨時(shí)數(shù)組 _pendingStateQueue
中讼撒。然后再調(diào)用 enqueueUpdate
方法。在 enqueueUpdate
方法中就出現(xiàn)了傳說中的batchingStrategy
批處理策略股耽。 在這里用 isBatchingUpdates
這個(gè)標(biāo)志來標(biāo)記是否處于一次 batchupdates
中根盒,如果不處于一次 batchupdates
,那么就會(huì)執(zhí)行 update
物蝙,否則炎滞,就會(huì)將當(dāng)前 component
存入 dirtyComponents
數(shù)組中。
根據(jù)以上的分析诬乞,就能知道册赛,為什么在一次 setState
執(zhí)行的時(shí)候,state
沒有立即更新震嫉。
同樣的森瘪,為什么最上面示例代碼的執(zhí)行結(jié)果是0,0责掏,2柜砾,3,是因?yàn)閮纱沃苯訄?zhí)行的 setState
和在 setTimeout
中的 setState
的調(diào)用棧不一樣换衬。直接執(zhí)行 setState
的時(shí)候痰驱,此時(shí)正處于一次 batchupdates
,所以并不會(huì)立即更新瞳浦,而是暫存担映,等同一批的 state
都合并之后,再一次性更新叫潦。