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
变化)应该怎么做呢?下一篇博文我们再来一同探究。