深入理解redux之reducer為什麼是純函式

LT_bear發表於2019-05-12

reducer是redux的三個核心概念之一,它指定了應用狀態的變化如何響應 actions 併傳送到 store,需要由開發人員自己定義,提起reducer,最常想到的一個準則是reducer要是純函式,那麼這其中是什麼原因呢,如果不是純函式的話會導致redux不可用嗎?接下來就來一步步分析下這個問題

reducer響應actions的原理

  我們定義的reducer負責接收action,並返回一個新的state,但在react元件開發中,我們也僅僅定義了而從未實際呼叫過reducer,所有reducer作用的原理是什麼呢?

  實際上,元件中我們會手動呼叫dispatch方法傳送action來響應事件,而這裡的傳送action不止傳送action那麼簡單,還包括了呼叫reducer,更新狀態為reducer的處理結果,觸發訂閱事件等一系列操作,都是在dispatch的方法內實現的,到這裡可以來通過原始碼的處理流程來了解一些這個過程~

function dispatch(action) {
    ...
    try {
      //將flag置為true,表明處於分發邏輯中
      isDispatching = true 
      
      //currentReducer即為傳入的reducer函式,這裡會自動呼叫currentReducer函式,並將返回值賦給currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    
    //呼叫訂閱函式
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
}
複製程式碼

  可以看到,dispatch處理中reducer方法的呼叫無疑是很關鍵的一步,經過它的處理之後才生成了新的狀態資料   在redux中,保持reducer是一個純函式非常重要,保證reducer是純函式要達到以下幾點:

  • 不得修改傳入的引數
  • 不得呼叫非純函式,如Date.now()
  • 不得執行有副作用的操作,如API請求和路由跳轉

那麼為什麼reducer函式要遵守這幾點規則呢,如果不遵守的話又會怎麼樣呢

純函式條件之一:不得修改傳入的引數

function reuderfunc(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return ...
    default:
      return state
  }
}
複製程式碼

  reducer接受的引數由兩個,一個是action,一個是state,其中action只是用來傳遞資訊的,完全沒有修改的必要,那麼state呢,既然reducer是用來返回一個新的state的,在這個過程中直接基於傳入的state來改,然後再返回,貌似也可以達到效果?   實際上,雖然在reducer處理過程中改變了傳入的state,有可能redux還會正常運轉,但像時間旅行,錄製和回放這類依賴於歷史狀態的功能則無法實現了,要知道這可是redux當時的設計初衷之一~   特別注意的是,如果改變了傳入的引數initState,或該物件底層的任意一個key值,都有可能在應用於react元件時,導致react任為該狀態無變化,而不更新元件,因為redux原始碼中將oldState和newState(reducer返回的結果)做比較,如果某一級state指定的引用相等,則會導致此結果,這樣做是犧牲一點計算效能(生成新物件)來保證頁面重新整理。

純函式條件之二:不得呼叫非純函式,如 Date.now() 或 Math.random()

redux的核心提供可預測化的狀態管理,即無論何時特定的action觸發的行為永遠保持一致,試想如果reducer中有Date.now()等非純函式,即使同樣的action,那麼reducer處理過程中也是有所不同的,不再能保證可預測性

純函式條件之二:執行有副作用的操作

  首先,執行有副作用的操作,如api和路由跳轉,因為設定到後臺的處理,會帶來和上一節同樣的問題,同樣的action觸發後的處理過程可能有所不同(依據後臺處理或路由跳轉而定),失去了可預測性   其次因為reducer函式的返回值是要作為下一個狀態值被返回的,那麼試想當reducer中有api呼叫時,api是會向後臺請求資料的非同步函式,往往希望後臺的請求結果資料會應用於新的state,但是這時候會發現,這個非同步函式解除安裝reducer中沒有辦法影響到state的更新,因為在非同步請求處理完成時,reducer函式已經被返回(函式的返回是同步的),所以說在redcuer中呼叫api也完全沒有意義了.

api請求該如何執行

  剛才提到在reducer中不能執行api請求操作,但是很明顯api操作是不可避免的,因為總要向後臺請求資料,那麼api請求應該如何做呢?這裡有兩個辦法

  • 在dispatch方法之前進行api請求:在dispatch之外先進行api非同步請求,當收到請求結果後,根據結果的不同選擇dispatch不同的action
  • 應用redux-thunk,redux-promise等中介軟體,就可以在dispatch函式中直接執行api請求等非同步操作了。

相關文章