setState何時同步更新狀態
官方文檔對setState這種同步行為語焉不詳,所以只能去看源代碼,港真,我真的不想去看React的源代碼,但是遇到這種事也沒有更好的辦法,畢竟,開源軟體的好處不就是可以去看源代碼嘛。
先直接說結論:
在React中,如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state。所謂「除此之外」,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval產生的非同步調用。
如果我們按照教科書般的方式來使用React,基本上不會觸及所謂的「除此之外」情況。
再說為什麼會這樣:
在React的setState函數實現中,會根據一個變數isBatchingUpdates判斷是直接更新this.state還是放到隊列中回頭再說,而isBatchingUpdates默認是false,也就表示setState會同步更新this.state,但是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改為true,而當React在調用事件處理函數之前就會調用這個batchedUpdates,造成的後果,就是由React控制的事件處理過程setState不會同步更新this.state。
上面的介紹是不是有點枯燥?我希望不要太枯燥,如果你不關心緣由,那就直接記住結論就行了。
為了展示效果,我們來看一段代碼,在 JS Bin 上,一個簡單的點擊按鈕增加計數的功能。
在onClick函數中,我們調用setState函數,然後在console.log上輸出this.state,由此判斷setState是否同步更新了this.state。
onClick() { this.setState({count: this.state.count + 1}); console.log("# this.state", this.state); }
在render函數中,我們用console.log輸出一個信息,通過render函數是否被執行,我們能夠判斷更新過程是否發生了,然後我們用三個按鈕分別代表三種事件處理方式。
render() { console.log("#enter render"); return ( <div> <div>{this.state.count} <button onClick={this.onClick}>Increment</button> <button id="btn-raw">Increment Raw</button> <button onClick={this.onClickLater}>Increment Later</button> </div> </div> ) }
最後顯示的界面是這樣。
Increment按鈕使用最正規的onClick方式處理點擊事件。
Increment Raw通過addEventListener處理點擊事件。
componentDidMount() { document.querySelector("#btn-raw").addEventListener("click", this.onClick); }
Increment Later通過setTimeout來處理點擊事件。
onClickLater() { setTimeout(() => { this.onClick(); }); }
通過點擊三個不同的按鈕,我們可以看到不同的行為。
點擊Increment,先輸出沒有更新的this.state,然後render函數被執行,可見this.state的更新是非同步的,更新過程也是在setState執行之後才引發。
但是如果點擊Increment Raw或者Increment Later,就是先執行render函數,然後輸輸出更新過的this.state,可見,this.state被同步更新了,而且在setState函數執行過程中更新過程就已經完成了。
你還希望setState同步更新this.state嗎?
上面的試驗很清楚地顯示,同步更新this.state的話,每一次調用setState都會引發同步的更新過程,這會更新過程很頻繁,也就會導致性能問題。
所以說,雖然React具有讓setState同步更新this.state的功能,我們還是避免這種使用方式。
別用這招,我們可以了解一種工具,但是並不表示我們就應該使用它。
推薦閱讀:
※CSS Modules入門Ⅲ:與React協同
※基於 JSX 的動態數據綁定
※React 從青銅到王者系列教程之倔強青銅篇
※如何評價React VR Project?