自定義 Redux 中的 combineReducers

簡介

Redux 中的 combineReducers 能讓咱們很方便地把多個 reducers 組合起來,成爲一個新的 reducer。
然而,隨着咱們的應用變得愈來愈複雜,combineReducers 有可能不能知足咱們的需求。
正如 Redux 官方文檔所說:html

This helper is just a convenience! You can write your own combineReducers that works differently, or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function.react

combineReducers 只是方便咱們使用而已,咱們能夠自定義一個徹底不一樣的 combineReducers 來知足咱們特殊的需求。git

原理

咱們先回憶一下 reducer 的寫法:github

const reducer = (oldState, action) => newState;

reducer 是一個普通的函數,接受兩個參數:oldStateaction,而後返回一個 newState
爲了把多個 reducers 組合起來,咱們一般會用 Redux 自帶的 combineReducers 來實現:redux

const rootReducer = combineReducers({
  key1: key1Reducer,
  key2: key2Reducer
});

先留意一下咱們傳了什麼給 combineReducers:api

{
  key1: function(state.key1, action) { /*...*/ },
  key2: function(state.key2, action) { /*...*/ },
}

好了,讓咱們先來想想,通過 combineReducers 的處理以後,咱們獲得了什麼呢?
不用想了,很顯然咱們獲得了一個新的 reducer。
那這個新的 reducer 又長什麼樣呢?函數

const rootReducer = (oldState, action) => newState;

你應該不會驚訝,由於全部 reducer 都長這個樣子,即便它是已經被組合過的 reducer,它也是長這個樣子。
如今你應該猜到 combineReducers 作了什麼了吧?其實它最基本形態是這樣子的:ui

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

它接受 reducers 做爲參數,而後返回一個標準的 reducer 函數。spa

注意:
其實到了這一步,咱們就能夠自定義 combineReducers 了,咱們徹底能夠寫一個相似的函數,而後在裏面寫各類 switch...case 語句來達到自定義的目的。
但我以爲咱們仍是先看看 Redux 自帶的 combineReducers 作了什麼比較好,由於咱們自定義的 combineReducers 頗有可能須要原來的功能。設計

還記得我剛纔叫你留意的地方嗎?沒錯,就是下面這個:

// reducers
{
  key1: function(state.key1, action) { /*...*/ },
  key2: function(state.key2, action) { /*...*/ }
}

咱們來回想一下 store.dispatch(action) 的過程:當一個 action 觸發的時候,全部 reducers 都應該響應這個 action,作出相應的改變,最後返回一個新的 store
對着上面這個結構,咱們其實很容易就能寫出這樣的效果,還能加上一些其餘的處理:

function reCombineReducers(reducers) {
  return function (state, action) {
    switch (action.type) {
      case SP_ACTION:
        return Object.assign({}, state, { /* do something */ });
      default:
        return Object.keys(reducers)
                    .map(k => ({ [k]: reducers[k](state[k], action) }))
                    .reduce((prev, next) => Object.assign({}, prev, next));
    }
  }
}

上面的例子模擬了原來 combineReducers 的功能,還對 SP_ACTION 進行了特殊的處理,很簡單吧!

然而,上面的例子雖然模擬了 combineReducers 的功能,但失去了 combineReducers 的檢查對象變化的功能,由於如今的 default block 中會返回一個全新的對象。
有沒有方法能夠既保留 combineReducers 的所有功能,又能擴展它呢?
其實很簡單,咱們只要利用 combineReducers 返回的函數就能夠了!
(感謝 liximomo 指出上面例子中的缺陷)

function reCombineReducers(reducers) {
  let fn = combineReducers(reducers);
  return function (state, action) {
    switch (action.type) {
      case SP_ACTION:
        return Object.assign({}, state, { /* do something */ });
      default:
        return fn(state, action);
    }
  }
}

實例

按照 Redux 的原則,不一樣的 reducer 應該相互獨立的,它們之間不該該有任何依賴。
這個原則看着是很美好的,但在實際使用中仍是會有一些例外的狀況。
一個很簡單的例子,也是我遇到過的例子,就是實現一個簡單的表格 (其實個人狀況複雜的多,須要實現相似 Excel 那樣的操做,同時支持其餘額外的功能)。
咱們先來設計一下 state:

// state
{
  rows: { ... },
  cells: { ... },
  data: { ... }
}

rows, cells, data 都會響應一些特定的 action (如 CHANGE_ROW_PROPS, CHANGE_CELL_PROPS, CHANGE_DATA),作出相應的改變,這些都是咱們所指望的。
然而,當出現一些特殊的 action (如 GET_TABLE_SUCCESS,表示成功從服務端獲取數據) 的時候,災難就出現了:
全部的 reducer 都須要監聽 GET_TABLE_SUCCESS 這個 action,這意味着若是咱們有 n 個 reducer 的話,咱們就須要修改 n 個文件!
當我再加上 UPDATE_TABLE_SUCCESSREMOVE_TABLE_SUCCESS 之類的 action 時,我要再修改 n 個文件!
這不合理啊,爲何我加一個簡單的功能,須要修改這麼多文件,最重要的是,這些修改都是很是相似!

這時候,咱們就須要自定義 combineReducers 來解決咱們的需求拉:

function reCombineReducers(reducers) {
  let fn = combineReducers(reducers);
  return function (state, action) {
    switch (action.type) {
      case GET_TABLE_SUCCESS:
      case UPDATE_TABLE_SUCCESS:
        return Object.assign({}, state, action.payload.table);
      case REMOVE_TABLE_SUCCESS:
        return initState;
      default:
        return fn(state, action);
    }
  }
}

const table = reCombineReducers({
  sections,
  suites,
  rows,
  cells,
  toys,
  data,
  logics
})

怎麼樣,是否是比修改多個文件舒服不少?

(完)

出處

http://scarletsky.github.io/2...

參考資料

http://redux.js.org/docs/api/...
https://github.com/reactjs/re...

相關文章
相關標籤/搜索