setState的兩種方式

在 React 文檔的 State and Lifecycle 一章中,其實有明確的說明 setState() 的用法,向 setState() 中傳入一個對象來對已有的 state 進行更新。 例如:html

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: this.state.count + 1
    };
  }
}
複製代碼

咱們若是想要對這個 state 進行更新的話,就能夠這樣使用 setState():vue

this.setState({
  count: 1
});
複製代碼

可是,在 React 的文檔下面,還寫着,處理關於異步更新 state 的問題的時候,就不能簡單地傳入對象來進行更新了。這個時候,須要採用另一種方式來對 state 進行更新。react

setState() 不只可以接受一個對象做爲參數,還可以接受一個函數做爲參數。函數的參數即爲 state 的前一個狀態以及 props。git

因此,咱們能夠向下面這樣來更新 state:github

this.setState((prevState, props) => ({ count: prevState.count + 1 }));
複製代碼

setState傳入函數與傳入對象的區別

傳入對象

爲了說明兩種方式的區別,咱們舉個比較簡單的例子。bash

咱們設置一個累加器,在 state 上設置一個 count 屬性,同時,爲其增長一個 increment 方法,經過這個 increment 方法來更新 count異步

此處,咱們採用給 setState() 傳入對象的方式來更新 state,同時,咱們在此處設置每調用一次 increment 方法的時候,就調用兩次 setState()函數

代碼以下:ui

class IncrementByObject extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
        this.increment = this.increment.bind(this);
    }
  
    // 此處設置調用兩次 setState()
    increment() {
        this.setState({
            count: this.state.count + 1
        });
        
        this.setState({
            count: this.state.count + 1
        });
    }
  
    render() {
        return (
            <div>
                <button onClick={this.increment}>IncrementByObject</button>
                <span>{this.state.count}</span>
            </div>
        );
    }
}
ReactDOM.render(
    <IncrementByObject />,
    document.getElementById('root')
);
複製代碼

此時點擊 button 的時候,count 就會更新了。可是,可能與咱們所預期的有所差異。咱們設置了點擊一次就調用兩次 setState(),可是,count 每一次卻仍是隻增長了 1,因此這是爲何呢?this

其實,在 React 內部,對於這種狀況,採用的是對象合併的操做,就和咱們所熟知的 Object.assign() 執行的結果同樣。

例如:

Object.assign({}, { a: 2, b: 3 }, { a: 1, c: 4 });
複製代碼

咱們最終獲得的結果將會是 { a: 1, b: 3, c: 4 }。對象

對象合併的操做,屬性值將會以最後設置的屬性的值爲準,若是發現以前存在相同的屬性,那麼,這個屬性將會被後設置的屬性所替換。因此,也就不難理解爲何咱們調用了兩次 setState() 以後,count 依然只增長了 1 了。

本質就是下面的代碼:

this.setState({
    count: this.state.count + 1
});
// 同理於
Object.assign({}, this.state, { count: this.state.count + 1 });
複製代碼

傳入函數

咱們將上面的累加器採用另外的方式來實現一次,在 setState() 的時候,咱們採用傳入一個函數的方式來更新咱們的 state。

class IncrementByObject extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
        this.increment = this.increment.bind(this);
    }
  
    // 此處設置調用兩次 setState()
    increment() {
        // 採用傳入函數的方式來更新 state
        this.setState((prevState, props) => ({
            count: prevState.count + 1
        }));
        this.setState((prevState, props) => ({
            count: prevState.count + 1
        }));
    }
  
    render() {
        return (
            <div>
                <button onClick={this.increment}>IncrementByObject</button>
                <span>{this.state.count}</span>
            </div>
        );
    }
}
ReactDOM.render(
    <IncrementByObject />,
    document.getElementById('root')
);
複製代碼

當咱們再次點擊按鈕的時候,就會發現,咱們的累加器就會每次增長 2 了。

在 React 的源代碼中,咱們能夠看到這樣一句代碼:

this.updater.enqueueSetState(this, partialState, callback, 'setState');
複製代碼

而後,enqueueSetState 函數中又會有這樣的實現

queue.push(partialState);
enqueueUpdate(internalInstance);
複製代碼

因此,與傳入對象更新 state 的方式不一樣,咱們傳入函數來更新 state 的時候,React 會把咱們更新 state 的函數加入到一個隊列裏面,而後,按照函數的順序依次調用。同時,爲每一個函數傳入 state 的前一個狀態,這樣,就能更合理的來更新咱們的 state 了。

結論

咱們在處理異步更新的時候,須要用到傳入函數的方式來更新咱們的 state。這樣,在更新下一個 state 的時候,咱們可以正確的獲取到以前的 state,並在在其基礎之上進行相應的修改。而不是簡單地執行所謂的對象合併。

另外最近正在寫一個編譯 Vue 代碼到 React 代碼的轉換器,歡迎你們查閱。

github.com/mcuking/vue…

相關文章
相關標籤/搜索