爲什麼說setState方法是異步的?

在學習或使用過一陣子React後,你可能會發現一個在setState方法的特性,如下面這個簡單例子來講明:html

export default class SelectBox extends React.Component {
  constructor(props) {
    super(props)
    this.state={value: ''}
  }

  handleChange = (e) => {
    this.setState({value: e.target.value})
    console.log(this.state.value)
  }

  render() {
      return(
         <div>
             <select onChange={this.handleChange} value={this.state.value}>
                <option value="JavaScript" key={1}>JavaScript</option>
                <option value="Angular2" key={2}>Angular2</option>
                <option value="React" key={3}>React</option>
             </select>
             <h1>{this.state.value}</h1>
         </div>
      )
   }
}

咱們在handleChange方法中,呼叫setState來更新選項的值,而後在控制檯中輸出這個值。看起來一切都是很符合邏輯,但你若是一執行就會發現,在控制檯中輸出的this.state.value,並不會在呼叫setState方法後當即就變更。像下面的執行的結果圖同樣:react

執行結果

固然,若是你直接輸出的是e.target.value,必定是正確的值,但在某些狀況下,咱們要取用的並非這個事件的值,而是要更動事後的state(狀態)值。git

若是要在setState方法後,直接取用更動後的state值,正確的使用方式,在官方文件中的說明,須要利用setState的第二傳參,傳入一個回調(callback)函式,改成像下面這樣的代碼:github

this.setState({value: e.target.value}, function(){ console.log(this.state.value) })

另外一個方式則是用componentDidUpdate()這個生命週期方法,把肯定state更新後要執行的代碼放在裏面,以下面的代碼:算法

componentDidUpdate(){
  console.log(this.state.value)
}

爲何必定要這樣做的主要緣由是:promise

setState這個方法,它在React中的執行行爲能夠認爲"異步的"異步

雖然setState並不是使用了setTimeout或promise的那種進入到事件迴圈(Event loop)的異步執行,但它的執行行爲在React庫中時,的確是異步的,也就是有延時執行的行爲。以官方文件中較精確的說法 - "它不是保證同步的"。async

setState方法與包含在其中的執行是一個很複雜的過程,這段程式碼從React最初的版本到如今,也有無數次的修改。它的工做除了要更動this.state以外,還要負責觸發從新渲染(render),這裏面要通過React核心中diff演算法,最終才能決定是否要進行重渲染,以及如何渲染。並且爲了批次與效能的理由,多個setState呼叫有可能在執行過程當中還須要被合併,因此它被設計以異步的或延時的來進行執行是至關合理的。oop

那麼setState會在什麼時候以同步的方式來執行,也就是當即更動this.state?答案是在React庫控制以外時,它就會以同步的方式來執行,在下面兩篇文章中,都有相似的例子:學習

但大部份的使用狀況下,咱們都是使用了React庫中的表單組件,例如select、input、button等等,它們都是React庫中人造的組件與事件,是處於React庫的控制之下,在這個狀況下,setState就會以異步的方式執行。因此通常來講,咱們會認爲setState就是異步執行,並非用原始碼來看它是否是有使用像setTimeoutPromise之類的方式轉爲JavaScript的異步執行方式,而是以它在React庫的控制之下,以執行行爲與順序來認定。

如下是翻自官方setState原代碼的註解,官網的說明也是相似:

不保證this.state會當即更新,因此在調用這個方法後存取this.state可能會回傳舊的值。

不保證呼叫setState就會同步地執行,而它們也可能最終被被批量調用(屢次呼叫的狀況下)。你能夠提供額外的回調(callback),回調(callback)將會在setState實際被完成時被執行。

所以,很早就有開發者提出來關於setState常令初學者感到怪異的執行狀況,在某些狀況下會形成執行後會看到不連續的結果。除了setState方法有異步執行的行爲外,它還有幾個被提出來的特殊行爲:

1. setState可能會引起沒必要要的渲染(renders)

state自己的設計是沒法直接更改,setState的設計是用來更動state值,也會觸發從新渲染(re-render),按照邏輯就是反正無論如何,只要開發者呼叫setState,React就去做整個視圖的從新渲染就是。因此setState一定會做從新渲染的執行,只是要如何渲染是由React來決定。

從新渲染(re-render)指的主要是頁面上視圖(View)的從新再呈現,這是React本來的核心設計,但這個設計是有一些問題的。最主要的是state(狀態)並不必定單純只用來記錄與視圖(View)有關的狀態,也有多是某個內部控制用的屬性值,或是隻套用在內部使用的資料。當你改變了這些與視圖無關的state(狀態)值,以如今的React設計來講,照樣要觸發從新渲染的執行過程,這在某些複雜的應用時,因爲形成沒必要要的渲染,也有可能形成效能上的問題。

固然,React提供了shouldComponentUpdate方法讓開發者能夠自行判斷,自行提供對應的解決方式。也有Performance Tools能夠進行剖析檢測。算得上是一些補強的做法。

2. setState沒法徹底掌控應用中全部組件的狀態

state(狀態)是獨立於每一個組件內部的,並且它是個不能直接更動的對象,這個設計固然是爲了要保持組件的封裝與獨立性,但因此若是當要開發一個複雜的應用時,一定須要使用那些能掌控全部組件資料,以及能提供各組件間資料互動的函式庫,例如Flux, Redux或MobX等等。

React組件目前只能透過各類生命週期的方法,與外部資源、計時器或DOM事件來進行掛勾(Hook),這些都沒法直接使用setState方法來進行管理,所以setState並無辦法徹底掌控一個應用中全部組件的狀態,它比較像是每一個組件中的都有的一種接口方法,單純要依靠setState方法來管控整個React應用,徹底是不足夠的。

以上說明參考自這篇文章: 3 Reasons why I stopped using React.setState

相關文章
相關標籤/搜索