在实际开发过程中,react通常会配合redux,react-router,redux-thunk等生态插件一起协同开发,尤其在使用redux的过程当中,会出现很多的模板代码的编写.比如你想修改页面上的一个状态的某个错误,你要去先找到action文件中触发的action,在该方法中寻找到actionCreator,随后你又要找到actionType,最后在reducer中找到对应的处理逻辑.不管是修改状态还是新增一个状态都要打开好几个文件进行处理,维护起来也是困难重重,大量的模板代码同时也降低了工作效率.
基于上述的原因,笔者希望能找到一种简化的方式书写代码,目的是将编写模板代码的那部分逻辑封装起来而不是让开发者去开发.于是提出了一个改造方案:开发者只需要编写一个对象,对象中包含initailState和getters和fearture三个属性.initailState就是初始化的状态,getters是获取状态的函数,最重要的部分便是feature对象.feature对象中包含很多函数,例如:
开发者如果编写好这些函数,通过这些函数名可以反推对应的action的函数名和reducer的函数名为updateNum,而在action中也可以推出对应的actionType为{type:`${namespace}/updateNum`},如此这样一来action和actionType都不用开发者自己去编写了.有人可能会说通过解析feature对象自动生成action和actionType只能完成同步操作,如果我需要在action中做些异步的操作或者其他的逻辑该怎么办呢?基于这样的需求我们可以在feature对象中这样改造:
在updateNum中编写两个函数一个对应action,另一个对应reducer.在action中你可以做各种各样的数据操作和异步操作,但是要将结果通过调用next函数传递给下一层.如果你想请求后端发送ajax请求,只需要你把ajax的请求参数放入next的参数中系统就自动帮你做异步的请求,最后你在你编写的reducer函数中获取数据结果并返回最终的state.
基于上面所述的构想开发出一个库simple-redux.js,利用它开发者只需要编写一个配置的对象,simple-redux会解析该对象自动生成我们想要的action,reducer和getters函数提供给外部使用.
simple-redux.js源码如下:
import axios from "axios";
export default class SimpleRedux {
static fail = 0;
static success = 2;
static loading = 1;
static born(data, _axios = null) {
const actions = SimpleRedux.generateActions(data, _axios);
const reducers = SimpleRedux.generateReducers(data);
const getters = SimpleRedux.generategetters(data);
return { actions, reducers, getters };
}
static toUpper(data) {
return data;
}
/**
*
* @param {*} param0
* 生成getters
*
*/
static generategetters({ initialState, getters = null, nameSpace }) {
if (getters !== null) { //用户自定义了getters
return getters;
}
let obj = {};
const state_arr = Object.keys(initialState);
state_arr.forEach((key) => {
let new_key = `get${key.slice(0, 1).toUpperCase()}${key.slice(1)}`;
obj[new_key] = (state) => {
return state[nameSpace][key];
}
})
return obj;
}
/**
*
* 生成reducer
*
* code -1 没有进行ajax请求
* 0 请求失败
* 1 请求中
* 2 请求成功
*/
static generateReducers({ nameSpace, feature, initialState }) {
return (state = initialState, action) => {
let fun = action.type.replace(new RegExp(`${SimpleRedux.toUpper(nameSpace)}|\/`, "ig"), "");
let code = null;
if (fun.includes("_requestting")) {
code = SimpleRedux.loading;
fun = fun.replace("_requestting", "");
} else if (fun.includes("_request_success")) {
code = SimpleRedux.success;
fun = fun.replace("_request_success", "");
} else if (fun.includes("_request_fail")) {
code = SimpleRedux.fail;
fun = fun.replace("_request_fail", "");
}
if (feature[fun]) {
let _fun = feature[fun];
if (feature[fun].reducer) {
_fun = feature[fun].reducer;
}
const new_state = _fun(state, action, code);
if (new_state) {
return new_state;
} else {
return state;
}
} else {
return state;
}
}
}
static toString(data) {
return Object.prototype.toString.call(data);
}
/**
* 生成actions
* action有三种情况
* 1.简单的同步操作
* 2.自定义异步操作
*/
static generateActions({ nameSpace, feature }, _axios) {
if (_axios === null) {
_axios = axios;
}
const arr = Object.keys(feature);
let actions = {};
arr.map((key) => {
if (SimpleRedux.toString(feature[key]) === "[object Function]") { //简单的同步操作
actions[key] = (...args) => {
return (dispatch) => {
dispatch({ type: SimpleRedux.toUpper(`${nameSpace}/${key}`), value: args });
return Promise.resolve(null);
}
}
} else if (SimpleRedux.toString(feature[key]) === "[object Object]") { //自定义操作
const { action } = feature[key];
actions[key] = (...args) => {
return async (dispatch, getState) => {
/**
* 在这里需要对参数进行分析
*
* 如果params是个对象并且携带了url,说明是ajax请求
*
*/
function next(params = null) {
return new Promise((resolve, reject) => {
if (SimpleRedux.toString(params) === "[object Object]" && params.url) { //这是一个异步请求
dispatch({ type: SimpleRedux.toUpper(`${nameSpace}/${key}_requestting`), args });
_axios({
...params,
method: params.method || 'post',
data: params.data || {}
}).then((res) => {
dispatch({ type: SimpleRedux.toUpper(`${nameSpace}/${key}_request_success`), value: res, args });
resolve(null);
}).catch((error) => {
dispatch({ type: SimpleRedux.toUpper(`${nameSpace}/${key}_request_fail`), value: error, args });
reject(error);
})
} else { //同步请求就简单多了
dispatch({ type: SimpleRedux.toUpper(`${nameSpace}/${key}`), value: params, args });
resolve(null);
}
})
}
try {
await action(next, getState, args, dispatch);
return Promise.resolve(null);
} catch (error) {
return Promise.reject(error);
}
}
}
}
})
return actions;
}
}
现在来讲讲在实际开发当中如何使用这个库,在你的redux文件下创建一个文件,引入simple-redux.js和你自己自定义的axios实例(如果没有不传递也可以,simple-redux会默认使用原生的axios).创建当前文件的命名空间,编写getters,initailState和feature.
如果只是一个同步操作只需要写一个函数就行了比如下面的updateStatus函数,updateStatus函数里面写的就是reducer当中的逻辑,通过action.value可以获取到页面上传递过来的参数.
如果是异步的操作比如下面的loadData就要写成一个对象里面包含action和reducer两个函数,至于具体的ajax请求的数据逻辑不需要在action中编写,你只需要给next提供你要做异步请求的参数就可以了,simple-redux通过检测next传递过来的参数发现如果没有包含url属性它就判断是一个同步请求并把这些数据直接传递给reducer.
如果发现包含了url就判定为一个异步请求了,此时simple-redux会做三个操作,在发送请求之前,它会派发dispatch({type:`${namespace/函数名_requetting}`}),请求成功会派发dispatch({type:`${namespace/函数名_success}`}),请求失败会派发dispatch({type:`${namespace/函数名_fail}`}),而这三个状态都可以在开发者编写的reducer函数中通过sr.loading,sr.success和sr.fail监听到.
具体使用如下:
import sr from "../../simple-redux";
import _axios from "../../../util/_axios";
export const nameSpace = "detailpage"; //命名空间
const data = {
initialState: {
isFetching: 0,
data: {}
},
getters: {
getData(state) {
return state[nameSpace].data;
},
getFlag(state) {
return state[nameSpace].isFetching;
}
},
nameSpace,
feature: {
updateStatus(state, action) {
return { ...state, isFetching: action.value[0]};
},
loadData: {
async action(next) {
await next({
url: "/data/detail.json",
method: "get"
})
},
reducer(state, action, code) {
switch (code) {
case sr.loading: return {
...state,
isFetching: 1
};
case sr.success:
return {
...state,
data: action.value,
isFetching:0
};
case sr.fail: return {
...state,
isFetching: 0
};
}
}
}
}
}
const result = sr.born(data, _axios);//第二个参数传入配置的axios实例,也可以不穿
export default result;
总结:最后谈一谈使用simple-redux的优劣处,好处自然是开发者可以少写很多的代码,只需要编写一个配置的对象参数,其他就全部交给simple-redux,它会解析参数动态生成actions,reducers,和getters给外部调用,actionType和actionCreator都是自动生成不用编写了.至于劣处呢就是要消耗一小部分性能为代价,原来我们编写redux代码的时候都是手写action和reducer,而现在这部份工作由simple-redux解析生成,当然如果不是超大型项目不会有特别的影响.