setState何時同步更新狀態

上次寫了一篇 setState為什麼不會同步更新組件狀態 - 知乎專欄,有朋友說,其實setState也可以同步更新this.state的,我研究了一下,真的耶!有些情況下setState真的可以同步更新this.state耶!

官方文檔對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?

TAG:React | 前端开发 | 同步 |