React 实践:改造 Redux action generator,编写 Redux reducer generator (1)
背景
从几个月前开始,写了一篇博文 编写自己项目的 Redux action generator,并且在项目实践中用上了,大大减少了编写 action 时的代码量,提高了开发效率。可是对于 reducer,还是需要手写很多东西,这其中也有很多类似的代码,因此,在上个项目告一段落后,开始构思如何通过类似的方式,用配置生成 reducer。
原有的写法
以项目中一个用户模块为例,它的 reducer 如下:
export default combineReducers({
list: (state = [], action) => {
switch (action.type) {
case getAction(NAME, 'list', defines.ACTION_STATUS_LOAD):
return [];
case getAction(NAME, 'list', defines.ACTION_STATUS_SUCCESS):
return Object.assign([], state, action.response.data.tenantAdmins);
default:
return state;
}
},
listLoading: (state = false, action) => {
switch (action.type) {
case getAction(NAME, 'list', defines.ACTION_STATUS_LOAD):
return true;
case getAction(NAME, 'list', defines.ACTION_STATUS_SUCCESS):
case getAction(NAME, 'list', defines.ACTION_STATUS_FAIL):
return false;
default:
return state;
}
},
···
其中,getAction 方法是获取全局唯一的 action 值,此处不细说。
思路
可以看到,list 和 listLoading 是很常见的 reducer,几乎在所有列表页面都会出现,而且是伴随着 list 这个 action 出现的。仔细去回忆不难发现,我们所有定义的 action,基本都会需要 reducer 与之配合使用。那么,我们就可以从 action 的配置出发,来生成一些通用的 reducer。
异步 action
这里指的是需要请求服务器获取数据的异步操作,我们原有的 action 是这么写的
{
name: 'list',
isAjax: true,
path: 'apis/notice',
method: 'GET',
msgType: defines.AJAX_TYPE_LOAD
}
根据需求的不同,我们在模块中可能会添加这些 reducer:
- listData
- listLoading
- listCurPage
- listTotal
- listFilter
其中,listCurPage 和 listFilter 需要额外的 action 与之配合使用。所以,我们添加以下配置,让异步 action 的配置能够自动生成我们想要的 action 和 reducer
data: {initialState, nextState(state, action)}
- 这个配置代表这个
action需要在其请求成功后存储数据到reducer中 - 约定生成的
reducer名称为${name}Data - initialState 配置该
reducer的初始值 nextState(state, action)是一个函数,配置触发getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)后reducer的响应- 代码
(其中,result[`${name}Data`] = (state = data.initialState, action) => { if (action.type === getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)) { return data.nextState(state, action); } return state; };result是最终传给combineReducers的一个对象,下文同理)
- 这个配置代表这个
loading: true
- 这个配置代表这个
action需要一个与之对应的loading状态标识 - 约定生成的
reducer名称为${name}Loading - 自动配置触发
getAction(prefix, name, defines.ACTION_STATUS_LOAD)时返回true, 触发getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)和getAction(prefix, name, defines.ACTION_STATUS_FAIL)时返回false - 代码
result[`${name}Loading`] = (state = false, action) => { if (action.type === getAction(prefix, name, defines.ACTION_STATUS_LOAD)) { return true; } if (action.type === getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)) { return false; } if (action.type === getAction(prefix, name, defines.ACTION_STATUS_FAIL)) { return false; } return state; };
- 这个配置代表这个
pagination: true
- 这个配置代表这个
action需要分页获取数据 - 约定生成
${name}CurPage和${name}Total两个reducer - 自动生成新的
action:set${toCamel(name)}Page(toCamel 函数是将传入的 name 转化为首字母大写的形式,为了符合整个驼峰式的要求,下文同理) - 约定该请求返回的数据中,要带上 count 代表数据的总量,在触发
getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)时,将该值设置到${name}Total中去 - 调用
set${toCamel(name)}Page时,${name}CurPage返回action.data action_generator 代码
Object.assign(result, { [getAction(actionPrefix, `set${toCamel(name)}Page`)]: getAction(actionPrefix, `set${toCamel(name)}Page`), [`set${toCamel(name)}Page`]: data => ({ type: getAction(actionPrefix, `set${toCamel(name)}Page`), data }) });(其中,
result是最终的 action 文件 export 出去的对象,包含所有的action,下文同理)- reducer_generator 代码
result[`${name}Total`] = (state = 0, action) => { if (action.type === getAction(prefix, name, defines.ACTION_STATUS_SUCCESS)) { return action.response.data.count; } return state; }; result[`${action.name}CurPage`] = (state = 1, action) => { if (action.type === getAction(prefix, `set${toCamel(name)}Page`)) { return action.data; } return state; };
- 这个配置代表这个
filter: true
- 这个配置代表这个
action需要筛选功能,在发出请求时带上筛选条件 - 约定生成的
reducer名称为${name}Filter - 自动生成
set${toCamel(name)}Filter和clear${toCamel(name)}Filter两个action - 约定
filter为一个Object,初始值为{} - 调用
set${toCamel(name)}Filter时,${name}Filter返回action.data - 调用
clear${toCamel(name)}Filter时,${name}Filter返回{} action_generator 代码
Object.assign(result, { [getAction(actionPrefix, `set${toCamel(name)}Filter`)]: getAction(actionPrefix, `set${toCamel(name)}Filter`), [`set${toCamel(name)}Filter`]: data => ({ type: getAction(actionPrefix, `set${toCamel(name)}Filter`), data }) }); Object.assign(result, { [getAction(actionPrefix, `clear${toCamel(name)}Filter`)]: getAction(actionPrefix, `clear${toCamel(name)}Filter`), [`clear${toCamel(name)}Filter`]: data => ({ type: getAction(actionPrefix, `clear${toCamel(name)}Filter`), data }) });- reducer_generator 代码
result[`${action.name}Filter`] = (state = {}, action) => { if (action.type === getAction(prefix, `set${toCamel(name)}Filter`)) { return action.data; } if (action.type === getAction(prefix, `clear${toCamel(name)}Filter`)) { return {}; } return state; };
- 这个配置代表这个
弹框类或切换显示类 action
这里指的是 action 需要控制某些组件的显示或隐藏,我们原有的 action 是这么写的
{
name: 'showForm'
},
{
name: 'hideForm'
},
{
name: 'toggleForm'
}
可以看到,这里需要写两个或三个 action,并且需要在 reducer 中去手动编写对应关系
formShow: (state = false, action) => {
switch (action.type) {
case getAction(NAME, 'showForm'):
return true;
case getAction(NAME, 'hideForm'):
return false;
case getAction(NAME, 'toggleForm'):
return !state;
default:
return state;
}
}
我们将其视为一种类型的 action
- 约定生成
show${toCamel(name)}hide${toCamel(name)}toggle${toCamel(name)}三个action用来控制 - 约定生成的 reducer 为
${name}Show action_generator 代码
Object.assign(result, { [getAction(actionPrefix, `show${toCamel(name)}`)]: getAction(actionPrefix, `show${toCamel(name)}`), [`show${toCamel(name)}`]: data => ({ type: getAction(actionPrefix, `show${toCamel(name)}`), data }) }); Object.assign(result, { [getAction(actionPrefix, `hide${toCamel(name)}`)]: getAction(actionPrefix, `hide${toCamel(name)}`), [`hide${toCamel(name)}`]: data => ({ type: getAction(actionPrefix, `hide${toCamel(name)}`), data }) }); Object.assign(result, { [getAction(actionPrefix, `toggle${toCamel(name)}`)]: getAction(actionPrefix, `toggle${toCamel(name)}`), [`toggle${toCamel(name)}`]: data => ({ type: getAction(actionPrefix, `toggle${toCamel(name)}`), data }) });- reducer_generator 代码
result[`${name}Show`] = (state = false, action) => { if (action.type === getAction(prefix, `show${toCamel(name)}`)) { return true; } if (action.type === getAction(prefix, `hide${toCamel(name)}`)) { return false; } if (action.type === getAction(prefix, `toggle${toCamel(name)}`)) { return !state; } return getExtraMapper(prefix, `${name}Show`, state, action, extras); };
设置类 action
这里指的是 action 需要设置某些值到 reducer 中,我们原有的 action 是这么写的
{
name: 'setSelected'
},
{
name: 'clearSelected'
}
可以看到,这里需要写两个 action,并且需要在 reducer 中去手动编写对应关系
selected: (state = '', action) => {
switch (action.type) {
case getAction(NAME, 'setSelected'):
return action.data;
case getAction(NAME, 'clearSelected'):
return '';
default:
return state;
}
}
我们将其视为一种类型的 action
- 约定生成
set${toCamel(name)}clear${toCamel(name)}两个action - 约定生成的 reducer 为
${name}Show action_generator 代码
Object.assign(result, { [getAction(actionPrefix, `set${toCamel(name)}`)]: getAction(actionPrefix, `set${toCamel(name)}`), [`set${toCamel(name)}`]: data => ({ type: getAction(actionPrefix, `set${toCamel(name)}`), data }) }); Object.assign(result, { [getAction(actionPrefix, `clear${toCamel(name)}`)]: getAction(actionPrefix, `clear${toCamel(name)}`), [`clear${toCamel(name)}`]: data => ({ type: getAction(actionPrefix, `clear${toCamel(name)}`), data }) });- reducer_generator 代码
result[`${name}`] = (state = initialState, action) => { if (action.type === getAction(prefix, `set${toCamel(name)}`)) { return action.data; } if (action.type === getAction(prefix, `clear${toCamel(name)}`)) { return initialState; } return getExtraMapper(prefix, `${name}`, state, action, extras); };
总结
以上就是通过 action 的配置来生成一些常用的 reducer 和 额外的 action 的做法,那不通过 action ,直接通过配置来生成 reducer 需要怎么做呢?想要往自动生成的 reducer 中增加自定义的转换(action 触发 reducer 变化)应该怎么做呢?下一篇博文我们再来一同探究。