關於React中的setState

在react中,setState是用以改變class組件狀態的函數,它有兩種用法:
一 傳入一個updater函數,該函數有兩個參數,一個是當前的state,還有一個是當前的props。該函數的返回值須要是一個更改的state值的對象,它將於state進行淺合併,其用法以下:html

this.setState((state, props) => {
        return { count: state.count + props.number };
    });

二 直接傳入一個對象:react

this.setState({ count: this.state.count + this.props.number });

setState函數還能夠接受第二個參數,該參數爲一個函數,將在更改的State生效以後調用:git

console.log(this.state.count); // 1
    this.setState({ count: 0 }, () => {
        console.log(this.state.count); // 0
    });
    console.log(this.state.count); // ? 此處便可能是1,也多是0

從上面代碼能夠看到,最後一行輸出的count是不固定的,這是爲何呢?
由於在react中,class內的事件處理程序會默認進行批處理,即若是你在componentDidMount裏面調用三次setState函數,那麼它最終會在componentDidMount執行完畢後,將三個State的更改合併爲一次調用。因此這時候setState就是異步的。
而在其餘場景下,setState將會是同步的,例如setTimeout內, Promise的then裏面。
一個簡單的例子:github

class SetStateExample extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    };

    this.onClick = this.onClick.bind(this);
  }

  componentDidMount() {
    console.log('componentDidMount before', this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log('componentDidMount after', this.state.count);
  }

  onClick() {
    console.log('onClick before', this.state.count)
    this.setState({ count: this.state.count + 1 }, () => {
      console.log('setState callback', this.state.count);
    });
    console.log('onClick after', this.state.count);
    Promise.resolve().then(() => {
      console.log('promise.then before', this.state.count);
      this.setState({ count: this.state.count + 1 });
      console.log('promise.then after', this.state.count);
      this.onClassEvent();
    });
  }

  onClassEvent() {
    console.log('onClassEvent before', this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log('onClassEvent after', this.state.count);
  }

  render() {
    return <div className="test">
      <div>count: {this.state.count}</div>
      <button onClick={this.onClick}>點擊改變count</button>
    </div>;
  }
}

讓咱們運行結果:promise

clipboard.png

首先第一第二行輸出是在componentDidMount裏面,咱們在函數內調用了setState,並在先後分別輸出了改變的值,結果代表,函數調用前與函數調用後該值並無當即改變,則代表在這裏setState是一個異步調用。那麼初步斷定在生命週期函數內部,setState是異步的調用。異步

而後第三第四行輸出是在onClick函數的回調裏面,該函數定義在class中,經過用戶點擊觸發。在setState調用先後咱們的輸出結果是一致的,這也代表其是一個異步調用。而在setState第二個參數中咱們輸出了改變後的count, 即第五行輸出,代表咱們的更改生效了。函數

而後第六行之後的輸出是咱們在onClick函數內調用了promise.resolve().then()輸出的,它是一個異步調用,react是沒法知道它何時執行,何時完成執行的,因此這時候react默認setState是同步的。從輸出咱們能夠看到每次更改以後,state的值都是當即變化生效的。性能

而在promise的回調內,咱們還調用了一個定義於class內的事件函數,可是該事件函數內的setState也是同步的形式。這說明了setState的同步或者異步與其定義位置並無直接的關係,而應該取決因而否由React直接進行調用,由於只有是React直接調用的狀況下,它才知道該函數何時執行完畢,才能進行批處理的優化。不然則默認是同步的調用。(具體內部實現就不展開了,由於我也不是特別懂HHHH,反正意思就大概是這麼個意思)優化

因此當在一些回調內部調用setState時應該注意將多個setState合併,由於它是同步的,屢次更新狀態會很影響性能。
以及須要注意進行異步調用的時候,若是須要使用變化後的值,請確保在異步調用完成後,通常是在setState的回調內,或者在componentDidUpdate鉤子內,可是請注意當心使用,由於很容易一不當心致使循環調用而崩潰。this

若是你想在本應同步調用的回調內,對setState進行異步調用,即讓它進行批處理,React也提供了一個API:

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

在unstable_batchedUpdates內部進行的setState會是異步調用,可是該API是不穩定的,由於後續的React版本更新中將會默認進行批處理即異步調用,屆時該API將被刪除。而這個後續的版本,極可能就是React 17

記錄與分享,歡迎斧正,虛心求教

參考鏈接:
https://stackoverflow.com/que...
https://react.docschina.org/d...
https://github.com/Advanced-F...
https://github.com/sisterAn/b...

相關文章
相關標籤/搜索