【React】setState詳解

不洗碗工作室發表於2019-03-04

作者:不洗碗工作室 -Pororo
出處:pororoj.github.io
總結自 setState何時同步更新狀態setState為什麼不會同步更新元件狀態

首先我們來看一個栗子:

class Eg extends Component {
  contructor () {
    super()
    this.state = {
      value: 0,
      index: 0
    }
  }

  componentDidMount () {
    this.setState({value: this.state.value + 1})
    console.log(this.state.value) // 第一次輸出
    this.setState({value: this.state.value + 1})
    console.log(this.state.value) // 第二次輸出
    setTimeout(() => {
      this.setState({value: this.state.value + 1})
      console.log(this.state.value) // 第三次輸出
      this.setState({value: this.state.value + 1})
      console.log(this.state.value) // 第四次輸出
    }, 0);
        this.refs.button.addEventListener(`click`, this.click)
  }

  click = () => {
    this.setState({value: this.state.index + 1})
    this.setState({value: this.state.index + 1})
  }

  render () {
    return (
      <div><span>value: {this.state.value}index: {this.props.index}</span>
        <button ref="button" onClick={this.click}>點選</button>
      </div>
    )
  }
}複製程式碼

這四次輸出,按常理來說分別是: 1,2,3,4。但是,實際輸出為: 0, 0, 2, 3。

在分析之前,需要知道setState的一些關鍵點。

setState的關鍵點

  1. setState不會立刻改變React元件中state的值

  2. setState通過引發一次元件的更新過程來引發重新繪製

    重繪指的就是引起React的更新生命週期函式4個函式:

    • shouldComponentUpdate(被呼叫時this.state沒有更新;如果返回了false,生命週期被中斷,雖然不呼叫之後的函式了,但是state仍然會被更新)
    • componentWillUpdate(被呼叫時this.state沒有更新)
    • render(被呼叫時this.state得到更新)
    • componentDidUpdate
  3. 多次setState函式呼叫產生的效果會合並。

     this.setState({name: `Pororo`})
     this.setState({age: 20})複製程式碼
     this.setState({name: `Pororo`,age: 20})複製程式碼

    上面兩塊程式碼的效果是一樣的。如果每次呼叫都引發一次生命週期更新,那效能就會消耗很大了。所以,React會將多個this.setState產生的修改放進一個佇列裡,等差不多的時候就會引發一次生命週期更新。

分析

知道了這些之後就開始分析。

先來分析前兩次setState:

this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第一次輸出
this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第二次輸出複製程式碼

由於setState不會立即改變React元件中state的值,所以兩次setState中this.state.value都是同一個值0,故而,這兩次輸出都是0。因而value只被加1。

既然這樣,那麼是不是可以直接操作this.state呢?比如:this.state.value=this.state.value+1;這樣的確可以修改this.state.value的狀態但是卻不可以引發重複渲染。所以,就必須通過React設定的setState函式去改變this.state,從而引發重新渲染。

接下來分析setTimeout裡面的兩次setState:

setTimeout(() => {
  this.setState({value: this.state.value + 1})
  console.log(this.state.value) // 第三次輸出
  this.setState({value: this.state.value + 1})
  console.log(this.state.value) // 第四次輸出
}, 0);複製程式碼

這兩次this.stat的值同步更新了,這是為什麼的呢?

在React中,如果是由React引發的事件處理(比如:onClick引發的事件處理),呼叫setState不會同步更新this.state,除此之外的setState呼叫會同步執行this.setState。 “除此之外”指的是:繞過React通過addEventListener直接新增的事件處理函式和setTimeout/setInterval產生的非同步呼叫。

那為什麼會這樣呢?

每次setState產生新的state會依次被存入一個佇列,然後會根據isBathingUpdates變數判斷是直接更新this.state還是放進dirtyComponent裡回頭再說。isBatchingUpdates預設是false,也就表示setState會同步更新this.state。但是,當React在呼叫事件處理函式之前就會呼叫batchedUpdates,這個函式會把isBatchingUpdates修改為true,造成的後果就是由React控制的事件處理過程setState不會同步更新this.state。

同步更新state的辦法—函式式setState

如果this.setState的引數不是一個物件而是一個函式時,這個函式會接收到兩個引數,第一個是當前的state值,第二個是當前的props,這個函式應該返回一個物件,這個物件代表想要對this.state的更改,換句話說,之前你想給this.setState傳遞什麼物件引數,在這種函式裡就返回什麼物件。不過,計算這個物件的方法有些改變,不再依賴於this.state,而是依賴於輸入引數state。

function increment(state, props) {
  return {count: state.count + 1};
}複製程式碼
function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState(increment);
}複製程式碼

假如當前this.state.count的值是0,第一次呼叫this.setState(increment),傳給increment的state引數是0,第二呼叫時,state引數是1,第三次呼叫是,引數是2,最終incrementMultiple讓this.state.count變成了3。

對於多次呼叫函式式setState的情況,React會保證呼叫每次increment時,state都已經合併了之前的狀態修改結果。

要注意的是,在increment函式被呼叫時,this.state並沒有被改變,依然,要等到render函式被重新執行時(或者shouldComponentUpdate函式返回false之後)才被改變。

把兩種setState的用法混用,會有什麼效果?

我們把incrementMultiple改成這樣。

function incrementMultiple() {
  this.setState(increment);
  this.setState(increment);
  this.setState({count: this.state.count + 1});
  this.setState(increment);
}複製程式碼

在幾個函式式setState呼叫中插入一個傳統式setState呼叫,最後得到的結果是讓this.state.count增加了2,而不是增加4。

這是因為React會依次合併所有setState產生的效果,雖然前兩個函式式setState呼叫產生的效果是count加2,但是中間出現一個傳統式setState呼叫,一下子強行把積攢的效果清空,用count加1取代。

所以,傳統式setState與函式式setState一定不要混用。

總結自setState何時同步更新狀態setState為什麼不會同步更新元件狀態

相關文章