本文作者:IMWeb jaychen 原文出处:IMWeb社区 未经同意,禁止转载
本文作者:IMWeb howenhuo 原文出处:IMWeb社区 未经同意,禁止转载
常见 Reducer 冗余
在使用 Redux 开发应用的过程中,我们经常会复制黏贴一些相似的 reducer
举个例子,有两个页面,它们都包含一个数据列表,它们都含有相同的状态
代码语言:javascript复制const page1State = {
loading: false, // 列表加载态(菊花)
retcode: 0, // 列表CGI返回码
page: 0, // 列表当前页码
count: 10, // 列表每页数量
pageTotal: 0, // 订单列表总共分页数量
list: [], // 列表
sort: -1 // 倒叙排序
};
const page2State = {
loading: false, // 列表加载态(菊花)
retcode: 0, // 列表CGI返回码
page: 0, // 列表当前页码
count: 10, // 列表每页数量
pageTotal: 0, // 订单列表总共分页数量
list: [], // 列表
title: "" // 标题
};
接着我们可能会编写这样的 reducer
代码语言:javascript复制function page1Reducer(state = page1State, { type, data }) {
switch (type) {
case PAGE1_LOADING_LIST: {
return Object.assign({}, state, {
loading: true
});
}
case PAGE1_GET_LIST: {
return Object.assign({}, state, {
loading: false,
page: data.page,
pageTotal: data.pageTotal,
list: data.list || []
});
}
case PAGE1_ERR_LIST: {
return Object.assign({}, state, {
loading: false,
retcode: data.retcode
});
}
default:
return state;
}
}
function page2Reducer(state = page2State, { type, data }) {
switch (type) {
case PAGE2_LOADING_LIST: {
return Object.assign({}, state, {
loading: true
});
}
case PAGE2_GET_LIST: {
return Object.assign({}, state, {
loading: false,
page: data.page,
pageTotal: data.pageTotal,
list: data.list || []
});
}
case PAGE2_ERR_LIST: {
return Object.assign({}, state, {
loading: false,
retcode: data.retcode
});
}
default:
return state;
}
}
区分不同的 action type 主要是避免 reduer 错误处理。
但这样看上去是否很冗余?那么是否可以只用一个 list reducer,就能处理这种重复的流程呢?
使用命名空间
首先我们将相同的状态抽取出来
代码语言:javascript复制const listState = {
loading: false, // 列表加载态(菊花)
retcode: 0, // 列表CGI返回码
page: 0, // 列表当前页码
count: 10, // 列表每页数量
pageTotal: 0, // 订单列表总共分页数量
list: [] // 列表
};
const page1State = {
...listState,
//其他字段
sort: -1 // 倒叙排序
};
const page2State = {
...listState,
//其他字段
title: "" // 标题
};
接着把前面两个 reducer 相同的部分提取出来
代码语言:javascript复制function listReducer(reducerNamespace) {
return (state, { namespace, type, data }) => {
// 初始调用或者命名空间不一致的都不做处理
if (state === undefined || reducerNamespace !== namespace) {
return state;
}
switch (type) {
case LOADING_LIST: {
return Object.assign({}, state, {
loading: true
});
}
case GET_LIST: {
return Object.assign({}, state, {
loading: false,
page: data.page,
pageTotal: data.pageTotal,
list: data.list || []
});
}
case ERR_LIST: {
return Object.assign({}, state, {
loading: false,
retcode: data.retcode
});
}
default:
return state;
}
};
}
这样原来的 Reducer 就简化成如下
代码语言:javascript复制const page1List = listReducer('PAGE1')
function page1Reducer(state = page1State, action) {
state = page1List(state, action);
switch (action.type) {
// 其他逻辑
default:
return state;
}
}
const page2List = listReducer('PAGE2')
function page2Reducer(state = page2State, action) {
state = page2List (state, action);
switch (action.type) {
// 其他逻辑
default:
return state;
}
}
最后我们在 dispatch 时,增加 namespace 字段就能指定处理哪一个列表的数据了
代码语言:javascript复制store.dispatch({
namespace: "PAGE1",
type:GET_LIST,
data: {
page: 1,
pageTotal: 10,
list: [1, 2, 3, 4, 5]
}
});
store.dispatch({
namespace: "PAGE2",
type: ERR_LIST,
data: {
retcode: 404
}
});
使用高阶函数抽象 reducer
虽然上面在 pageReducer 中已经抽象出相同的 listReducer,但写法上不是很优雅,我们再实现一个 composeReducers
代码语言:javascript复制function composeReducers(...reducers) {
return (state, action) => {
if (reducers.length === 0) {
return state;
}
const last = reducers[reducers.length - 1];
const rest = reducers.slice(0, -1);
return rest.reduceRight(
(enhanced, reducer) => reducer(enhanced, action),
last(state, action)
);
};
}
利用这个 composeReducers 可以把原来 pageReducer 中耦合的 listReducer 分离出来,并且可以轻松的组合多个 Reducer
代码语言:javascript复制export default combineReducers({
page1Reducer: composeReducers(page1Reducer, listReducer("PAGE1")),
page2Reducer: composeReducers(page2Reducer, listReducer("PAGE2"))
});
例子源码
codesandbox
参考
Reducer 逻辑复用
重用 Redux 中的 reducer