理解react之setState

組件狀態(state)是一種持有,處理和使用信息的方式。state包含的信息僅做用於一個給定組件的內部,並容許你根據它實現組件的一些邏輯。state一般是一個POJO(Plain Old Java[Script] Object)對象,改變它是使得組件從新render本身的方式之一。react

state是react背後原理的重要基礎概念之一,可是它也有一些特色使得它用起來會有點難以捉摸而且有可能會致使在你的應用中出現一些預料以外的行爲。ajax

更新State
惟一你能直接寫this.state的地方應該是組件的構造函數中。在其它全部地方你都應該使用this.setState函數,它接受一個對象做爲參數,這個對象最終會被合併到組件的當前狀態中。安全

而在技術上你是能夠經過this.state={//a new object}這種方式直接修改狀態的,可是它不會引發組件使用新的值去從新渲染,而後致使狀態不一致的問題。異步

setState是異步的
事實上setState會引發的一致性處理(reconciliation)——從新渲染組件樹的過程,是下一個屬性的基礎即setState是異步的。這容許咱們在單個做用域內屢次調用setState而不會觸發沒必要要的從新渲染整個樹。ide

這就是爲何在你更新state後並不能立馬看見新的值。函數

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

React 也會嘗試將屢次setState調用組合或者批處理爲一次調用:學習

// 假設 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。那麼怎麼獲得指望值3呢?this

setState接受一個函數做爲它的參數
若是你傳遞一個函數做爲setState的第一個參數,React將會使用在當前調用時刻的state去調用它並指望你返回一個對象合併到state中。因此你能夠把咱們上面的代碼改爲下面這樣便可:翻譯

// 假設 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了,就和上面咱們認爲指望值應該是3相一致了。
記住當在更新state爲一個值的時候應始終使用這種語法,它的計算是基於前面的一個狀態的。code

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);
}

點擊按鈕元素將會致使你的console中顯示:

// click!
before: 1
after: 1
// click!
before: 2
after: 2

但若是咱們添加如下代碼:

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

咱們將在console中看到:

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

所以,咱們須要學習何時指望獲得哪一種行爲嗎?並非如此。假設setState的確是異步是很是安全的,由於在將來它就是如此

setState接受一個回調函數
若是你須要執行一些函數,或者驗證狀態是否真的有更新正確。你還能夠給setState傳遞一個函數做爲第二個參數,這個函數會在狀態更新完畢後獲得執行。請記住因爲單個塊內的全部更新會被合併成一個,這將致使每一個setState中的回調中獲得的state值是全更新的state。

另一種能夠保證你的代碼執行是在更新完成之後的方式是將執行代碼放在componentWillUpdate 或者 componentDidUpdate中。然而對比回調函數的方式,這兩個方法會在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>
  }
}

若是它的父組件這樣render它:

<Component value={42} />

它將會正確渲染value爲42,但若是父組件中修改爲以下:

<Component value={13} />

那它仍會認爲this.state.value是42,這是由於React並不會銷燬組件並從新建立它——它會重用一旦渲染好的組件,而且不會從新執行構造函數。要避免這個問題,你應該不要將props賦值給state,而應在render方法中使用this.props.value。
若是你仍是想要使用state(若是你的props是以一種很是複雜的計算的使用模式,你不但願每一次render都執行這些複雜的計算),你還能夠實現一種在須要的時候纔去更新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*函數都不是一個合適的地方去觸發side effect(如ajax請求),因此請使用
componentDidUpdate(previousProps, previousState),一樣也提供和上面相似的if防禦語句確保在沒有變化時不會執行相關代碼。

寫在最後的話:這篇文章在Medium中得到超過2.1K的贊,挺不錯的,值得翻譯! 翻譯若有不正,歡迎留言指出!

相關文章
相關標籤/搜索