標籤:

精讀 React functional setState

本期精讀文章: Functional setState is the future of React

1 引言

眾所周知,React 組件中的 this.state 和 this.props 存在非同步模式更新的情況,我們是沒辦法直接用他們的值計算下一個 state。出於性能方面的考慮 React 並不會在 setState({…newState}) 後直接更新 DOM,而是以一種批量更新的模式進行。但這對於需要需要當前 state 來計算新的 state 時就很麻煩,functional setState 很好的解決了這個問題。

2. 內容概要

傳統的 setState

作為一個基於狀態的 UI 庫,React 就像是一個狀態機將組件的所有狀態映射為 UI 元素。React 自身有一個管理狀態的函數 setState,開發者可以將一個對象,也就是需要更新的狀態作為參數傳入。

class User { constructor () { this.state = { score : 0 }; } // multiple setState() calls increaseScoreBy3 () { this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1}); this.setState({score : this.state.score + 1}); } render () { return ( <div> <button onClick={this.increaseScoreBy3.bind(this)}>+++</button> <h5>This user scored {this.state.score}</h5> </div> ); }}

在上述的代碼示例中,在我們調用了 increaseScoreBy3 方法後卻不會將 state 更新為 3。正如文中一個形象的類比,『我不會爬到山峰三次,每一次都去更新一個狀態』。

我們知道如果 shouldComponentUpdate 返回 true 時(默認返回為 true),setState 後會依次調用 4 個生命周期方法:

  1. shouldComponentUpdate
  2. componentWillUpdate
  3. render()
  4. componentDidUpdate

出於到性能方面的考量,React 避免了 3 次更新,在其內部使用了類似下面的方式來更新 state:

const singleObject = Object.assign( {}, objectFromSetState1, objectFromSetState2, objectFromSetState3);

由於 state 非同步的原因導致了調用 increaseScoreBy3 方法後,組件的 score 狀態為 1。

Functional setState 的意義

Functional setState 很好的解決了上面我們遇到的問題。

class User{ state = {score : 0}; //let"s fake setState setState(state, callback) { this.state = Object.assign({}, this.state, state); if (callback) callback(); } // multiple functional setState call increaseScoreBy3 () { this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ), this.setState( (state) => ({score : state.score + 1}) ) }}const Justice = new User();

這裡同樣引用 Dan 的一句好來說明 functional setState 的意義『多次調用 setState 時使用函數作為參數更加保險,React 會將所有的更新組成一個隊列,然後按照他們調用的順序來執行』。這樣就避免了將 state 合併成一個對象的問題。

此外,文中還提到了一個使用 functional setState 的最佳實踐,將這些用於處理狀態的純函數放到組件類的外部。

這樣更有助於組件的關注度分離,組件不需要在去關心如何更新狀態的問題,它需要做的就是表達需要更新的意願。當然還有一個好處就是這些純函數很方便開發者做測試。

3. 精讀

更新狀態方式

setState 接收 2 種不同的參數類型,分別是對象和匿名函數。

對象作為參數時,React 內部會以一種對象合併的方式來批量更新組件的狀態,就和我們所熟知的 Object.assign() 執行的結果類似,這種方式在狀態更新是很好的做到了節流。但這種非同步的 state 也就帶來了另一個問題,沒法使用當前的 state 來計算新的 state 值。

函數作為參數時,則是將所有的更新組成一個隊列,然後按照他們調用的順序來執行:

...this.updater.enqueueSetState(this, partialState, callback, "setState");...queue.push(partialState);enqueueUpdate(internalInstance);

這樣也就保障了執行隊列中每一個函數時都能拿到最新的狀態,用於計算新的狀態。

分離 action

將組件的 action 分離,這樣組件內部就不需要去關心狀態的更新方式。這樣的實踐更偏向於命令式。在更加複雜的組件設計中,可以更大程度的提升組件的復用性,同時保障了組件代碼的純粹。

import {increaseScore} from "../stateChanges";class User{ ... // inside your component class handleIncreaseScore () { this.setState( increaseScore)} ...}

4. 總結

並不是所有的場景都適合使用 functional setState 方式。如果需要處理非同步的 state 確實會可以獲取到之前的 state 並在其基礎上進行操作,這樣更加的安全。如果需要的只是簡單的對象合併,那繼續選擇對象參數的 setState 方式也是無可厚非。至於純函數的測試以及分離組件的 action 也是只 functional setState 的加分項。


推薦閱讀:

基於 Webpack 的應用包體尺寸優化
React 實現一個漂亮的 Table
React Conf 2017 不能錯過的大起底——Day 1!
解析 Redux 源碼

TAG:React |