深刻理解redux之reducer爲何是純函數

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

reducer響應actions的原理

  咱們定義的reducer負責接收action,並返回一個新的state,但在react組件開發中,咱們也僅僅定義了而從未實際調用過reducer,全部reducer做用的原理是什麼呢?redux

  實際上,組件中咱們會手動調用dispatch方法發送action來響應事件,而這裏的發送action不止發送action那麼簡單,還包括了調用reducer,更新狀態爲reducer的處理結果,觸發訂閱事件等一系列操做,都是在dispatch的方法內實現的,到這裏能夠來經過源碼的處理流程來了解一些這個過程~api

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是純函數要達到如下幾點:promise

  • 不得修改傳入的參數
  • 不得調用非純函數,如Date.now()
  • 不得執行有反作用的操做,如API請求和路由跳轉

那麼爲何reducer函數要遵照這幾點規則呢,若是不遵照的話又會怎麼樣呢bash

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

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處理過程當中也是有所不一樣的,再也不能保證可預測性dom

純函數條件之二:執行有反作用的操做

  首先,執行有反作用的操做,如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請求等異步操做了。
相關文章
相關標籤/搜索