combineReducers原理

簡介

隨著應用變得復雜,需要對 reducer 函數 進行拆分,拆分后的每一塊獨立負責管理 state 的一部分。
Redux 中的 combineReducers 能讓我們很方便地把多個 reducers 組合起來,成為一個新的 reducer。

原理

combineReducers輔助函數的作用是,把一個由多個不同 reducer 函作為 value 的 object,合并成一個最終的 reducer 函數,然后就可以對這個 reducer 調用 createStore

合并后的 reducer 可以調用各個子 reducer,并把它們的結果合并成一個 state 對象。state 對象的結構由傳入的多個 reducer 的 key 決定

state的最終結構為:

{
  reducer1: ...,
  reducer2: ...
}

通過上述的講解我們可以知道,combineReducecrs的基本型態應該是這樣的:

function combineReducers(reducers) {
  return function (state, action) { /*...*/ };
}

它接受 reducers 作為參數,然后返回一個標準的 reducer 函數。

下面我們就來看一下combineReducers的源碼解析:

import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'

//根據key和action生成錯誤信息
function getUndefinedStateErrorMessage(key, action) {
  //...
}

//一些警告級別的錯誤
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
  const reducerKeys = Object.keys(reducers)
  const argumentName = action && action.type === ActionTypes.INIT ?
    'preloadedState argument passed to createStore' :
    'previous state received by the reducer'

  //判斷reducers是否為空數組
  //判斷state是否是對象
  //給state中存在而reducer中不存在的屬性添加緩存標識并警告
  //...
}


//這個方法用于檢測用于組合的reducer是否是符合redux規定的reducer
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    //調用reducer方法,undefined為第一個參數
    //使用前面說到過的ActionTypes.INIT和一個隨機type生成action作為第二個參數
    //若返回的初始state為undefined,則這是一個不符合規定的reducer方法,拋出異常
    //...
  })
}

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers) //所有的鍵名
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    //finalReducers是過濾后的reducers,它的每一個屬性都是一個function
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }

  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let sanityError

  //檢測每個reducer是否是符合標準的reducer
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

    //如果不是成產環境,做一些警告判斷
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {} //下一個state樹

    //遍歷所有reducers,然后將每個reducer返回的state組合起來生成一個大的狀態樹,所以任何action,redux都會遍歷所有的reducer
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)

      //如果此reducer返回的新的state是undefined,拋出異常
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    //如果當前action對應的reducer方法執行完后,該處數據沒有變化,則返回原來的流程樹
    return hasChanged ? nextState : state
  }
}

源碼解釋:

  • 這是一個標準的 reducer 函數,接受一個初始化狀態 和 一個 action 參數;
  • 每次調用的時候會去遍歷 finalReducers (有效的 reducer 列表),獲取列表中每個 reducer 對應的先前狀態: var previousStateForKey = state[key]
  • 看到這里就應該明白傳入的 reducers 組合為什么 key 要和 store 里面的 state 的 key 相對應;
  • 然后得到當前遍歷項的下一個狀態: var nextStateForKey = reducer(previousStateForKey, action)
  • 然后把它添加到整體的下一個狀態: nextState[key] = nextStateForKey
  • 每次遍歷會判斷整體狀態是否改變: hasChanged = hasChanged || nextStateForKey !== previousStateForKey
  • 在最后,如果沒有改變就返回原有狀態,如果改變了就返回新生成的狀態對象: return hasChanged ? nextState : state
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容