React 中 setState() 爲何是異步的?

前言

不知道你們有沒有過這個疑問,React 中 setState() 爲何是異步的?我一度認爲 setState() 是同步的,知道它是異步的以後非常困惑,甚至期待 React 能出一個 setStateSync() 之類的 API。一樣有此疑問的還有 MobX 的做者 Michel Weststrate,他認爲常常聽到的答案都很容易反駁,並認爲這多是一個歷史包袱,因此開了一個 issue 詢問真正的緣由。最終這個 issue 獲得了 React 核心成員 Dan Abramov 的回覆,Dan 的回覆代表這不是一個歷史包袱,而是一個通過深思熟慮的設計。react

注意:這篇文章根據 Dan 的回覆寫成,但不是一篇翻譯。我忽略了不少不過重要的內容,Dan 的完整回覆請看這裏git

正文

Dan 在回覆中表示爲何 setState() 是異步的,這並無一個明顯的答案(obvious answer),每種方案都有它的權衡。可是 React 的設計有如下幾點考量:github

1、保證內部的一致性

首先,我想咱們都贊成推遲並批量處理重渲染是有益並且對性能優化很重要的,不管 setState() 是同步的仍是異步的。那麼就算讓 state 同步更新,props 也不行,由於當父組件重渲染(re-render )了你才知道 props安全

如今的設計保證了 React 提供的 objects(state,props,refs)的行爲和表現都是一致的。爲何這很重要?Dan 舉了個栗子:性能優化

假設 state 是同步更新的,那麼下面的代碼是能夠按預期工做的:bash

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2
複製代碼

然而,這時你須要將狀態提高到父組件,以供多個兄弟組件共享:網絡

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // 在父組件中作一樣的事
複製代碼

須要指出的是,在 React 應用中這是一個很常見的重構,幾乎天天都會發生。異步

然而下面的代碼卻不能按預期工做:性能

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
複製代碼

這是由於同步模型中,雖然 this.state 會當即更新,可是 this.props 並不會。並且在沒有重渲染父組件的狀況下,咱們不能當即更新 this.props。若是要當即更新 this.props (也就是當即重渲染父組件),就必須放棄批處理(根據狀況的不一樣,性能可能會有顯著的降低)。優化

因此爲了解決這樣的問題,在 React 中 this.statethis.props 都是異步更新的,在上面的例子中重構前跟重構後都會打印出 0。這會讓狀態提高更安全。

最後 Dan 總結說,React 模型更願意保證內部的一致性和狀態提高的安全性,而不老是追求代碼的簡潔性。

2、性能優化

咱們一般認爲狀態更新會按照既定順序被應用,不管 state 是同步更新仍是異步更新。然而事實並不必定如此。

React 會依據不一樣的調用源,給不一樣的 setState() 調用分配不一樣的優先級。調用源包括事件處理、網絡請求、動畫等。

Dan 又舉了個栗子。假設你在一個聊天窗口,你正在輸入消息,TextBox 組件中的 setState() 調用須要被當即應用。然而,在你輸入過程當中又收到了一條新消息。更好的處理方式或許是延遲渲染新的 MessageBubble 組件,從而讓你的輸入更加順暢,而不是當即渲染新的 MessageBubble 組件阻塞線程,致使你輸入抖動和延遲。

若是給某些更新分配低優先級,那麼就能夠把它們的渲染分拆爲幾個毫秒的塊,用戶也不會注意到。

3、更多的可能性

Dan 最後說到,異步更新並不僅關於性能優化,而是 React 組件模型能作什麼的一個根本性轉變(fundamental shift)。

Dan 仍是舉了個栗子。假設你從一個頁面導航到到另外一個頁面,一般你須要展現一個加載動畫,等待新頁面的渲染。可是若是導航很是快,閃爍一下加載動畫又會下降用戶體驗。

若是這樣會不會好點,你只須要簡單的調用 setState() 去渲染一個新的頁面,React 「在幕後」開始渲染這個新的頁面。想象一下,不須要你寫任何的協調代碼,若是這個更新花了比較長的時間,你能夠展現一個加載動畫,不然在新頁面準備好後,讓 React 執行一個無縫的切換。此外,在等待過程當中,舊的頁面依然能夠交互,可是若是花費的時間比較長,你必須展現一個加載動畫。

事實證實,在如今的 React 模型基礎上作一些生命週期調整,真的能夠實現這種設想。@acdlite 已經爲這個功能努力幾周了,而且很快會發佈一個 RFC(亦可賽艇!)。

須要注意的是,異步更新 state 是有可能實現這種設想的前提。若是同步更新 state 就沒有辦法在幕後渲染新的頁面,還保持舊的頁面能夠交互。它們之間獨立的狀態更新會衝突。

Dan 最後對 Michel 說到:我但願咱們能在接下來幾個月說服你,而且你會欣賞到 React 模型的靈活性。據我理解,這種靈活性至少一部分要歸功於 state 的異步更新。

相關文章
相關標籤/搜索