我是如何一步步“改造”redux的
从Vue换到React+Redux进行开发已经有半年多的时间,总的来说体验是很好的,对于各种逻辑和业务组件的抽象实在是方便的不行,高阶组件,洋葱模型等等给我带来了很多编程思想上的提升。但是在使用Redux开发的过程中还是感觉不太顺手,本文将阐述我是如何对Redux进行一步步“改造”以适应个人和团队开发需求的。 问题在使用Redux开发的过程中逐渐发现,虽然我们已经将UI组件和业务组件尽可能的进行抽离,尽可能的保证reduceractions的复用性, 是的,Redux对我来说,太复杂了 针对一个简单的操作,我们需要进行以下步骤来完成: 1.定义action export const CHANGE_CONDITION = 'CHANGE_CONDITION' 2.定义一个对应的action创建函数 export const changeCondition = condition => ({ type: CHANGE_CONDITION,condition }) 3.引入action,定义reducer,在复杂的switch语句中,对对象进行更改 import { CHANGE_CONDITION } from '@actions' const condition = (state = initCondition,action) => { switch(action.type) { case CHANGE_CONDITION: return ... default: return state } } 4.在需要时,引入action创建函数,并将对应的state进行连接 import { changeCondition } from 'actions' @connect(...) 我只是想做一个简单的状态修改呀! 可能我们会说,这样拆分能够保证我们整个项目的规范化,增强业务的可预测性与错误定位能力。 而且,针对请求的修改,我们往往要把action拆分成START,SUCCESS,FAILED三种状态,reducer里需要进行三次修改。而且往往 所以说,我们如何在保证redux的设计原则以及项目规范性上,对其进行“简化改造”,是我这里需要解决的问题。 使用middleware简化请求针对请求的处理,我之前也写过一篇文章优雅地减少redux请求样板代码,通过封装了一个redux中间件react-fetch-middleware 大致思路如下: 1.action创建函数返回的内容为一个包含请求信息的对象,并包含需要分发的三个action,这三个action可以通过actionCreator进行创建 import { actionCreator } from 'redux-data-fetch-middleware' // create action types export const actionTypes = actionCreator('GET_USER_LIST') export const getUserList = params => ({ url: '/api/userList',params: params,types: actionTypes,// handle result handleResult: res => res.data.list,// handle error handleError: ... }) 2.在redux中间件中,针对以上格式的action进行处理,首先进行请求,并分发请求开始的action, const applyFetchMiddleware = ( fetchMethod = fetch,handleResponse = val => val,handleErrorTotal = error => error ) => store => next => action => { // 判断action的格式 if (!action.url || !Array.isArray(action.types)) { return next(action) } // 获取传入的三个action const [ START,FAILED ] = action.types // 在不同状态分发action,并传入loading,error状态 next({ type: START,loading: true,...action }) return fetchMethod(url,params) .then(ret => { next({ type: SUCCESS,loading: false,payload: handleResult(ret) }) }) .catch(error => { next({ type: FAILED,error: handleError(error) }) }) } 3.将reducer进行对应的默认处理,使用reducerCreator创建的函数中自动进行对应处理,并且提供二次处理的机制 const [ GET,GET_SUCCESS,GET_FAILED ] = actionTypes // 会在这里自动处理分发的三个action const fetchedUserList = reducerCreator(actionTypes) const userList = (state = { list: [] },action => { // 二次处理 switch(action.type) { case GET_SUCCESS: return { ...state,action.payload } } }) export default combineReducers({ userList: fetchedUserList(userList) }) 再进一步,简化Redux Api经过前一步对请求的简化,我们已经可以在保证不改变redux原则和书写习惯的基础上,极大的简化请求样板代码。 很高兴看到这个库: Rematch 但是有些功能和改进并不是我们想要的,因此我仅对我需要的功能和改进点进行说明,并用自己的方式进行实现。我们来一步步看看 1.冗长的switch语句针对reducer,我们不希望重复的引用定义的各个action,并且去掉冗长的switch判断。其实我们可以将其进行反转拆分,将每一个action定义为标准化的reducer,在其中对state进行处理. const counter = { state: 1,reducers: { add: (state,payload) => state + payload,sub: (state,payload) => state - payload } } 2.复杂的action创建函数去掉之前的action和action创建函数,直接在actions中进行数据处理,并与对应的reducer进行match export const addNum = num => dispatch => dispatch('/counter/add',num) 我们会看到,与reducer进行match时,我们使用了'/counter/add'这种命名空间的方式, 我们可以通过增强的combinceReducer进行命名空间的设定: const counter1 = { ... } const counter2 = { ... } const counters = combinceReducer({ counter1,counter2 }) const list = { ... } // 设置大reducer的根命名空间 export default combinceReducer({ counters,list },'/test') // 我们可以通过这样来访问 dispatch('/test/counters/counter1/add') 3.别忘了请求针对请求这些异步action,我们可以参考我们之前的修改,dispatch一个对象 export const getList = params => dispatch => { return dispatch({ //对应到我们想要dispatch的命名空间 action: '/list/getList',url: '/api/getList',params,handleResponse: res => res.data.list,handleError: error => error }) } 同时,我们在reducer中进行简单的处理即可,依旧可以进行默认的三个状态处理 const list = { // 定义reducer头,会自动变为getList(开始请求),getListSuccess,getListFailed // 并进行loading等默认处理 fetch: 'getList' state: { list: [] },reducers: { // 二次处理 getListSuccess: (state,payload) => ({ ...state,list: payload }) } } 与项目进行整合我们会看到,我们已经将redux的api进行了极大的简化,但是依旧保持了原有的结构。目的有以下几点:
原有的数据流变成了这样: 因此,我们是在redux的基础上进行二次封装的,我们依然保证了原有的Redux数据流,保证数据的可回溯性,增强业务的可预测性与错误定位能力。这样能极大的保证与老项目的兼容性,所以我们需要做的,只是对action和reducer的转化工作 1.combinceReducer返回原格式的reducer我们通过新的combinceReducer,将新的格式,转化为之前的reducer格式,并保存各个reducer其和对应的action的命名空间。 代码简单示意: //获取各reducers里的方法 const actionNames = Object.keys(reducers) const resultActions = actionNames.map(action => { const childNamespace = `${namespace}/${action}` // 将action存入namespace Namespace.setActionByNamespace(childNamespace) return { name: Namespace.toAction(childNamespace),fn: reducers[action] } }) // 返回默认格式 return (state = inititalState,action) => { // 查询action对应的新的reducer里的方法 const actionFn = resultActions.find(cur => cur.name === action.type) if (actionFn) { return actionFn.fn && actionFn.fn(state,action.payload) } return state } 2.新的action创建函数最终dispatch出原格式的action我们需要把这样格式的函数,转化成这样 count => dispatch => dispatch('/count/add',count) //or params => dispatch => { dispatch('/count/add',1),dispatch('/count/sub',2) } //结果 count => ({ type: 'count_add',payload: count }) 这里的处理比较复杂,其实就是改造我们的dispatch函数 action => params => (dispatch,getstate) => { const retDispatch = (namespace,payload) => { return dispatch({ type: Namespace.get(namespace),payload }) } return action(params)(retDispatch,getstate) } 总结通过对Redux Api的改造,相当于二次封装,已经很大的简化了目前在项目中的样板代码,并且在项目中很顺畅的使用。 针对整个过程,其实还有几个可以改进的地方:
有兴趣的话,欢迎探讨~ 附上github easy-redux (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |