深刻理解React中的setState

組件的狀態是一種保存、處理和使用給定組件內部信息的方法,並容許你實現其自身的邏輯。狀態自己實際上是JavaScript中一個簡單的對象(Plain Old Java[Script] Object),而且改變它是使組件從新進行渲染的幾種方法之一。異步

這是React背後最基本的思路之一,可是它(狀態)有一些使用起來很棘手的屬性,可能會致使應用程序出現意外行爲。函數

更新狀態

組件中的構造函數是惟一一個你能夠直接寫this.state的地方,而在其餘地方你應該使用this.setStatesetState將接受最終合併到組件當前狀態的一個對象或方法做爲參數。post

雖然技術上能夠經過直接寫入this.state來改變狀態,但它不會致使組件使用新數據從新渲染,而且一般會致使狀態的不一致。this

setState是異步的

setState致使協調(從新渲染組件樹的過程)的事實是基於下一個屬性 — setState是異步的。這容許咱們在單個範圍內屢次調用setState,而不是觸發不須要從新渲染整個組件樹。code

這就是爲何在更新後沒有在狀態中看到新值的緣由。component

// assuming this.state = { value: 0 }
this.setState({
  value: 1
});
console.log(this.state.value); // 0

React還會嘗試將setState分組調用或批量調用到一個回調中,這會致使咱們第一次「陷阱」。對象

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

上面全部的調用過程結束後,this.state.value的值是1,而不是咱們所指望的3。爲了解決這個問題 …ip

setState接受一個方法做爲它的參數

若是你在setState中傳入一個函數做爲第一個參數,React將以 at-call-time-current狀態來調用它,並指望你返回一個對象來合併到狀態中。因此更新咱們以上的代碼:get

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

最終的結果將如咱們所指望的this.state.value = 3記住在將狀態更新爲值時始終使用此語法,該值是根據之前的狀態計算的!回調函數

爲何setState是異步的?

記住你是如何知道setState是異步的?嗯,事實證實並不是老是如此!這取決於執行上下文,例如:

render() {
    return <button onClick={this.inc}>Click to update</button>
  }
  
  inc() {
    console.log('before: ' + this.state.test);
    this.setState({
      test: this.state.test+1
    });
    console.log('after: ' + this.state.test);
  }

點擊按鈕,而後在控制檯的顯示結果多是:

// click!
before: 1
after: 1

// click!
before: 2
after: 2

可是若是咱們新增如下代碼:

componentDidMount() {
  setInterval(this.inc, 1000);
}

結果是:

before: 1
after: 2
before: 2
after: 3

因此,咱們應該學會什麼時候期待什麼行爲嗎?顯然不是。能夠確定的是,假設setState確實是異步的,由於在未來的版本中它將是如此。

setState接受一個回調函數

若是你須要執行某個函數,或驗證狀態確實是否正確更新,你能夠傳遞一個函數做爲setState調用的第二個參數,一旦狀態更新,該函數將被執行。記住,由於在一個範圍內的全部更新是批量的,若是你有屢次調用setState,則將使用徹底更新後的狀態調用每一個回調。

在更新發生後確保代碼執行的另外一種方法是將其放在componentWillUpdatecomponentDidUpdate中。然而,當使用shouldComponentUpdate來阻止更新的時候,相反上兩個鉤子函數是不用去使用的。

常見錯誤

其中最多見的一個錯誤是在構造函數中基於屬性(props)來使用組件的state設置它的值。考慮如下的代碼:

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: this.props.value };
  }

  render() {
    return <div>The value is: {this.state.value}</div>
  }
}

若是其父組件以下面這樣對它進行渲染:

<Component value={42} />

將正確渲染「The value is 42」,若是父組件改變了

<Component value={13} />

最後this.state.value的值依然是42,這是由於React並不會銷燬和從新建立這個組件,它將重用一次渲染的組件,不會從新運行構造函數。爲了解決這個,你不該將props分配給state而是在render方法中使用this.props.value。然而若是你決定使用state,你應該實現一個解決方案,它將在須要時更新狀態,例如:

class Component extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: this.props.value };
  }
  componentWillReceiveProps(nextProps) {
    if(nextProps.value !== this.props.value) {
      this.setState({value: nextProps.value});
    }
  }
  render() {
    return <div>The value is: {this.state.value}</div>
  }
}

記住任何componentWill *函數都不是觸發反作用的地方(例如進行AJAX調用),因此對於上面的狀況請使用componentDidUpdate(previousProps, previousState)

附錄

咱們能夠預期setState會隨着React Fiber及其餘更改而發生一些變化。正如前面提到的,在大多數狀況下setState是異步的,
另外一個變化是使用函數語法能夠終止「進行中」的setState調用:

this.setState((state) => {
  if(checkSomeConditions()) return undefined;
  else return { value: 42}
});
相關文章
相關標籤/搜索