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 是一個普通的函數,接受兩個參數:oldState
和 action
,而後返回一個 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_SUCCESS
,REMOVE_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...