天天看點

React之setState的正确打開方式

React官方文檔中提到:

NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains. setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate().

If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

下面的代碼分别是官方推薦和不推薦的兩種改變state的方式:

class Dog extends Component {
    constructor(props) {
        super(props)
        this.state = {
            color: 'white',
            age: 7,
            son: {
              color: 'gray',
              age: 1
            }
        }
    }
    
    brushHair() {
      //right
      setState({color: 'white'})
      
      //wrong
      this.state.color = 'black';
    }
}
           

但你會發現在實踐中即便使用第二種寫法也不會報任何錯誤,甚至還能得到預期結果,那麼為什麼這種方式會被诟病呢?下面我談談自己的了解:

  1. React中state發生變化,元件就會更新。setState()被設計為異步的,其目的在于在某些情況下(如一次onClick中)延緩并将多次更新合并為一次進而優化元件性能,如果像第二種寫法那樣直接改變state的值,這種優化也就不存在了;
  2. 執行setState會按照React設計的執行順序,調用其内部的各種函數,如果直接改變state,這些本應被調用的函數沒有執行,可能會導緻奇奇怪怪的錯誤;
  3. 因為setState()是異步的,是以上面的代碼最終color的值可能是white,顯然不符合正常的思維邏輯;

有時候我們希望在setState之後立即使用state的值,有兩種方法可以實作這個需求:

方法一:

setState({ color: 'red' }, () => console.log(this.state.color));
           

即向setState傳入一個回調函數,這個函數會在state發生變化之後被調用。

方法二:

setState({ age: 18 });
setState((prevState, props) => ({
  age: ++prevState.age
}))
           

setState的第一個參數還可以是一個函數,這個函數的傳回值就是要merge的state,差別在于這個setState會在之前的setState執行完畢後再執行,是以prevState是最新的state。

另外,當更新state的部分屬性時,其他屬性是不會受影響的,本質上是Object.assign({}, state, partialState),但僅限于第一層結構,如果像下面這樣

brushSonHair() {
    setState({
        son: {
            color: 'black'
        }
    })
}
           
上面的方法中setState改變的時第二層的屬性值(son中的color),第一層的屬性值(color age)不會受到影響,但son中的age屬性就會丢失(可以了解為改變了son)。

繼續閱讀