React 实践:编写自己项目的 Redux action generator
背景
使用 React
+ Redux
也有一段时间了,从第一次尝试使用之后就开始按一种方式写代码,思路很清晰,但就是有一个问题,感觉每写一个 action 都是在 copy
+ paste
,然后修改其中的部分内容,最近想优化代码所以尝试着编写一个 generator
来生成这些 actions
,让代码更加优雅。
原有的写法
这是一个 action 的部分代码
export const USER_LIST_LOAD = 'USER_LIST_LOAD';
export const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS';
export const USER_LIST_FAIL = 'USER_LIST_FAIL';
exports.loadUserList = (tenantId, sucCallback, errCallback) =>
(dispatch, getState) =>
dispatch({
[CALL_API]: {
types: [
USER_LIST_LOAD,
USER_LIST_SUCCESS,
USER_LIST_FAIL
],
endpoint: `apis/${tenantId}/user`,
schema: Schemas.NORMAL_RESP,
method: 'GET',
errMsg: '获取数据失败,请刷新重试',
sucCallback,
errCallback
}
});
export const USER_LIST_SELECT = 'USER_LIST_SELECT';
exports.userListSelect = keys => ({
type: USER_LIST_SELECT,
keys
});
思路
- 每一个
action
无非就是 export 出在全局唯一的常量作为 action 的标识,并且提供可以调用的方法来 dispatch 这样的常量。这里的 action 分为两类,ajax 请求类和非 ajax 请求类,ajax 请求类会 export 3 个常量,分别代表 请求中,请求成功以及请求失败 3 个状态。 - 传进来的参数,应该要统一成一个(ajax 请求的数据都放在第一个参数里,第二个和第三个固定为成功回调和失败回调)。
- 为了保证 action 的唯一性,我们需要传入一个参数作为前缀,一般用页面名作为前缀(这里是 USER)。
- 为了用配置的方式写 actions,考虑把 actions 用 json 数组的方式传进来,这样可以用到 map 函数来遍历并且生成每一个 action。
- 最后要将所有的 action 组装成一个变量 export 出去,再对原有的 import 方法进行修改就可以了。
generator 代码
首先,根据是否 ajax 请求,生成对应的 action
const actionList = (actionPrefix, actions) =>
map(action =>
action.isAjax ? ({
[`${actionPrefix}_${action.name.toUpperCase()}_LOAD`]:
`${actionPrefix}_${action.name.toUpperCase()}_LOAD`,
[`${actionPrefix}_${action.name.toUpperCase()}_SUCCESS`]:
`${actionPrefix}_${action.name.toUpperCase()}_SUCCESS`,
[`${actionPrefix}_${action.name.toUpperCase()}_FAIL`]:
`${actionPrefix}_${action.name.toUpperCase()}_FAIL`,
[action.name]: (data, sucCallback, errCallback) =>
(dispatch, getState) =>
dispatch({
[CALL_API]: {
types: [
`${actionPrefix}_${action.name.toUpperCase()}_LOAD`,
`${actionPrefix}_${action.name.toUpperCase()}_SUCCESS`,
`${actionPrefix}_${action.name.toUpperCase()}_FAIL`
],
endpoint: action.path,
schema: action.schema || Schemas.NORMAL_RESP,
method: action.method,
errMsg: action.errMsg || '获取数据失败,请刷新重试',
data,
sucCallback,
errCallback
}
})
}) : ({
[`${actionPrefix}_${action.name}`]:
`${actionPrefix}_${action.name}`,
[action.name]: data => ({
type: `${actionPrefix}_${action.name}`,
data
})
})
)(actions);
- 传进来的参数有两个,一个
actionPrefix
,一个包含每个 action 信息的数组 - 对每个 action 来说,
isAjax
用来区分是否 ajax 请求;name
是 action 的名字,需要是唯一的;path
和method
分别代表 ajax 请求的地址和方法(get、post 等) - 这里的
map
函数来自于ramda
库
然后,将上面生成的 action 数组合并起来并 export 出去
export default (actionPrefix, actions) => {
const result = {};
forEach(action =>
Object.assign(result, action)
)(actionList(actionPrefix, actions));
return result;
};
- 这里定义了该文件 export 一个函数,接收两个参数
- 这个函数能够先通过传进来的 actions 生成一系列方法和变量,然后组装起来
- 这里的
forEach
函数来自于ramda
库
最后,真正的 action 文件只需这样 配置
就能实现原有代码的功能
import generator from './generator';
export default generator('USER', [
{
name: 'list',
isAjax: true,
path: 'apis/user',
method: 'GET'
},
{
name: 'listSelect'
}
];
新的问题
如果 action
的 path
里需要通过部分参数来组装时,就需要再优化 generator
了
const getPath = (path, data) => {
const replaceStrings = path.match(/\$\{\w*\}/g);
if (!replaceStrings) return path;
let result = path;
forEach(replaceString =>
result = result.replace(replaceString,
data[replaceString.substring(2, replaceString.length - 1)])
)(replaceStrings);
return result;
};
先定义这样一个方法,然后再将原有函数的 endpoint
修改一下
endpoint: getPath(action.path, data),
- 这个函数是利用正则,将
path
中符合${param}
格式的参数用data
里面对应的key
的值替换掉,这样就实现了原有的apis/${tenantId}/user
ES6 模板字符串的写法 - 要保证调用
action
时候的第一个参数,即data
,含有这些需要转换的参数
最终实现这样的 action,只需要这样配置
{
name: 'list',
isAjax: true,
path: 'apis/${tenantId}/user',
method: 'GET'
}
调用时候的示例
import userActions from '../../actions/user';
userActions.list({
tenantId: '11223344'
});
总结
- 经过了这样的改造,代码真的优雅了不少,真正实现了用配置的方式来编写业务,很有成就感。对于这样的项目来讲,只要定好规范,新人很快上手写
action
不是梦。 - 平时写的代码多了,就要多注意这些一直在
copy
和paste
的代码,看看是否能够抽取出公共的部分来让代码更加优雅,这才是编程的艺术。 - 写一个
generator
没那么容易,需要考虑各种情况,还要结合实际权衡,让其能更加通用,具备较强可复用性。另外,反复地斟酌和优化代码是必不可少的过程。