react之setState解析

在react開發過程中,state是組件一個重要的屬性,對state的管理也尤爲重要,這裏記錄一下踩坑經歷

修改state正確方式

對於修改組件state正確的方式如下:

// 錯誤
this.state.title = 'React';

// 正確
this.setState({title: 'React'});

有兩個比較重要的點,一是對state的修改是異步,二是對多個相鄰的state的修改可能會合併到一起一次執行。代碼舉例:

異步執行

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

  handleClick() {
    this.setState((prevState, props) => ({
      num: prevState.num + 1
    }));
    console.info(this.state.num);
  }

  render() {
    return (
      <div>
        <h1 onClick={this.handleClick.bind(this)}>Hello, world!</h1>
        <h2>It is {this.state.num}.</h2>
      </div>
    );
  }
}

ReactDOM.render(<Clock />, document.getElementById("root"));

結果在ui顯示每一次加1之後的結果,而console打印的總是上一次ui的值,也就可以確定state並沒有立即執行,而是異步去執行的。我們可以通過添加一個回調函數到第二個參數,讓state更新完成之後來執行該回調,代碼如下:

handleClick() {
    this.setState({
      num : this.state.num + 1
    });
  }

此時ui上顯示的數值與console打印的會是一致。另一種情況,如果對state的修改依賴state和prop,那麼由於異步的原因就會產生很多異常情況,我們可以可以傳入包含兩個參數的函數到第一個參數來解決,代碼如下:

handleClick() {
    this.setState((prevState, props) => ({
      num: prevState.num + 1
    }),() => console.info(this.state.num));

  }

第一個參數prevState表示前一次的state,後一個參數表示當前狀態組件的prop值,顯示的讓該操作同步執行

批量單次執行

對於多次相鄰的state修改操作的執行會被合併在一起執行,代碼如下:

handleClick() {
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
    this.setState({
      num : this.state.num + 1
    },() => console.info(this.state.num));
  }

最終數值每次只會加1,我們可以看作是以下代碼的執行:

Object.assign(
  previousState,
  {num : this.state.num + 1},
  {num : this.state.num + 1}
)

因此,被合併之後最終只會保留一個更新。解決方法和上述解決異步的方式類似:

handleClick() {
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
    this.setState((prevState,props) => ({
      num : prevState.num + 1
    }),() => console.info(this.state.num));
  }

結果是我們期望的每次加3,但是需要注意的是,console打印的只會是每次加3之後的數值,並不會是每次加1的結果

State與Immutable

React官方建議把State當作是不可變對象,一方面是如果直接修改this.state,組件並不會重新render;另一方面State中包含的所有狀態都應該是不可變對象。當State中的某個狀態發生變化,我們應該重新創建這個狀態對象,而不是直接修改原來的狀態。那麼,當狀態發生變化時,如何創建新的狀態呢?根據狀態的類型,可以分成三種情況:

  1. 狀態的類型是不可變類型(數字,字符串,布爾值,null, undefined)
    這種情況最簡單,因爲狀態是不可變類型,直接給要修改的狀態賦一個新值即可。如要修改count(數字類型)、title(字符串類型)、success(布爾類型)三個狀態:

    this.setState({
    count: 1,
    title: 'Redux',
    success: true
    })
  2. 狀態的類型是數組
    如有一個數組類型的狀態books,當向books中增加一本書時,使用數組的concat方法或ES6的數組擴展語法(spread syntax):

    // 方法一:將state先賦值給另外的變量,然後使用concat創建新數組
    var books = this.state.books; 
    this.setState({
    books: books.concat(['React Guide']);
    })
    // 方法二:使用preState、concat創建新數組
    this.setState(preState => ({
    books: preState.books.concat(['React Guide']);
    }))
    // 方法三:ES6 spread syntax
    this.setState(preState => ({
    books: [...preState.books, 'React Guide'];
    }))

    當從books中截取部分元素作爲新狀態時,使用數組的slice方法:

    // 方法一:將state先賦值給另外的變量,然後使用slice創建新數組
    var books = this.state.books; 
    this.setState({
    books: books.slice(1,3);
    })
    // 方法二:使用preState、slice創建新數組
    this.setState(preState => ({
    books: preState.books.slice(1,3);
    }))

    注意不要使用push、pop、shift、unshift、splice等方法修改數組類型的狀態,因爲這些方法都是在原數組的基礎上修改,而concat、slice、filter會返回一個新的數組。

  3. 狀態的類型是普通對象(不包含字符串、數組)
    3.1 使用ES6 的Object.assgin方法

    // 方法一:將state先賦值給另外的變量,然後使用Object.assign創建新對象
    var owner = this.state.owner;
    this.setState({
    owner: Object.assign({}, owner, {name: 'Jason'});
    })
    // 方法二:使用preState、Object.assign創建新對象
    this.setState(preState => ({
    owner: Object.assign({}, preState.owner, {name: 'Jason'});
    }))

    3.2 使用對象擴展語法

    // 方法一:將state先賦值給另外的變量,然後使用對象擴展語法創建新對象
    var owner = this.state.owner;
    this.setState({
    owner: {...owner, name: 'Jason'};
    })
    // 方法二:使用preState、對象擴展語法創建新對象
    this.setState(preState => ({
    owner: {...preState.owner, name: 'Jason'};
    }))

參考:
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/state-and-lifecycle.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章