上一篇中的Action只是個數(shù)據(jù)的載體,用于告知Reducer發(fā)生了什么事情,真正搞事情的還得靠Reducer,在Reducer里更新Store里的state。本篇就介紹一下Reducer,源碼已上傳Github,第三篇源代碼請參照src/originReduxReducer和src/originReduxCombineReducer文件夾。
Reducer應(yīng)該是個純函數(shù),即只要傳入相同的參數(shù),每次都應(yīng)返回相同的結(jié)果。不要把和處理數(shù)據(jù)無關(guān)的代碼放在Reducer里,讓Reducer保持純凈,只是單純地執(zhí)行計(jì)算。
Reducer接收兩個參數(shù):舊的state和Action,返回一個新的state。即(state, action) => newState
。有兩個注意點(diǎn):一是首次執(zhí)行Redux時(shí),你需要給state一個初始值。二是根據(jù)官網(wǎng)的說明,Reducer每次更新狀態(tài)時(shí)需要一個新的state,因此不要直接修改舊的state參數(shù),而是應(yīng)該先將舊state參數(shù)復(fù)制一份,在副本上修改值,返回這個副本。
第一點(diǎn)的板式語法是在Reducer的函數(shù)聲明里,用es6的默認(rèn)參數(shù)給state賦初值。第二點(diǎn)的板式語法是用es6的結(jié)構(gòu)賦值將舊state復(fù)制一份:
return {
...state,
// 更新state中的值
};
在第二篇的源代碼基礎(chǔ)上繼續(xù)修改源代碼,將Reducer獨(dú)立到reducers目錄中,目錄結(jié)構(gòu)變?yōu)椋?/p>
reducers/number.js:
import * as constant from '../configs/action';
const initialState = {
number: 0,
};
export default (state = initialState, action) => {
switch (action.type) {
case constant.INCREMENT:
return {
...state,
number: state.number + 1,
};
case constant.DECREMENT:
return {
...state,
number: state.number - 1,
};
case constant.CLEAR_NUM:
return {
...state,
number: 0,
};
default:
return state;
}
};
entries/originReduxReducer.js:
// 改前
const reducer = (state, action) => {
if (typeof state === 'undefined') {
return 0;
}
switch (action.type) {
case constant.INCREMENT:
return state + 1;
case constant.DECREMENT:
return state - 1;
case constant.CLEAR_NUM:
return 0;
default:
return state;
}
};
const store = createStore(reducer);
// 改后
import reducer from '../reducers/number';
const store = createStore(reducer);
最終結(jié)果和前兩篇是一樣的,如下圖數(shù)字會跟隨點(diǎn)擊的按鈕發(fā)生變化。例子本身的結(jié)果不重要。重要的是代碼的結(jié)構(gòu)更加工程化。
上一篇Action有個Action Creator,Reducer也有個Reducer Creator概念,用switch-case比較Action.type代碼太low了。這里的low不是指用es6的語法就高大上了,而是指代碼會不夠清晰,代碼是寫給人看的,順便讓機(jī)器跑一下。我們用Reducer Creator改寫一下上面的Reducer代碼。
抽出個lib/common.js,里面定義個createReducer共同函數(shù):
export const createReducer = (initialState, handlers) => {
return (state = initialState, action) => {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action);
} else {
return state;
}
}
};
如果你對函數(shù)式編程不熟悉,我啰嗦幾句解釋一下。createReducer本質(zhì)上是返回一個函數(shù)對象。返回的匿名函數(shù)簽名和Reducer函數(shù)簽名是一樣的,等于是封裝了Reducer。在匿名函數(shù)內(nèi)匹配Action.type,并返回一個和Reducer函數(shù)簽名一樣的另一個匿名函數(shù)。如果匹配不到Action.type,就返回state,這意味著你在寫業(yè)務(wù)代碼的Reducer時(shí),甚至都不用考慮switch-case里default的問題。不知道我解釋清楚了沒有,不明白的話,多看幾遍就能睡著了…
現(xiàn)在Reducer里可以不用switch-case,用createReducer專注于業(yè)務(wù)邏輯了:
import * as constant from '../configs/action';
import { createReducer } from '../lib/common';
const initialState = {
number: 0,
};
export default createReducer(initialState, {
[constant.INCREMENT]: (state, action) => {
return {
...state,
number: state.number + 1,
};
},
[constant.DECREMENT]: (state, action) => {
return {
...state,
number: state.number - 1,
};
},
[constant.CLEAR_NUM]: (state, action) => {
return {
...state,
number: 0,
};
},
});
Reducer既然是用于根據(jù)業(yè)務(wù)邏輯更新state,那如何切分業(yè)務(wù)是個問題。Redux基于此,提供了combineReducers方法,可以將多個Reducer合并成一個。參數(shù)是多個Reducer的key-value對象:
combineReducers({
reducer1: myReducer1,
reducer2: myReducer2,
});
但通常會選擇用Reducer名直接作為key,因此可以寫成:
combineReducers({
myReducer1,
myReducer2,
});
例如,我們?yōu)轫撁嬖黾右粋€和數(shù)字不同的業(yè)務(wù),點(diǎn)擊按鈕顯示alert提示:
目錄結(jié)構(gòu)更新為:
reducers/index.js:
import { combineReducers } from 'redux';
import changeNumber from './number';
import toggleAlert from './alert';
export default combineReducers({
changeNumber,
toggleAlert,
});
現(xiàn)在Store里的state的結(jié)構(gòu)變成:
讀取state值時(shí):
store.getState().changeNumber.number
store.getState().toggleAlert.showAlert
很簡單,combineReducers就這點(diǎn)內(nèi)容。補(bǔ)充一句,combineReducers并沒規(guī)定只能連接到頂層Reducer里,你可以根據(jù)實(shí)際的業(yè)務(wù)邏輯封裝出任意層級的Reducer。這樣業(yè)務(wù)代碼的封裝性和可讀性會變的更好。