React之setState的正確開啟方式

sakuragizhx發表於2017-07-05

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)。

相關文章