前言Redux是一个非常实用的状态管理库,对于大多数使用React库的开发者来说,Redux都是会接触到的。在使用Redux享受其带来的便利的同时, 我们也深受其问题的困扰。redux的问题之前在另外一篇文章Redux基础中,就有提到以下这些问题纯净。Redux只支持同步,让状态可预测,方便测试。 但不处理异步、副作用的情况,而把这个丢给了其他中间件,诸如redux-thunkredux-promiseredux-saga等等,选择多也容易造成混乱啰嗦。那么写过Redux的人,都知道actionreducer以及你的业务代码非常啰嗦,模板代码非常多。但是,这也是为了让数据的流动清晰明了。性能。粗暴地、级联式刷新视图(使用react-redux优化)。分型。原生 Redux-react 没有分形结构,中心化 store里面除了性能这一块可以利用react-redux进行优化,其他的都是开发者不得不面对的问题,对于代码有洁癖的人,啰嗦这一点确实是无法忍受的。方案目标如果你使用过VUEX的话, 那么对于它的API肯定会相对喜欢很多,当然,vuex不是immutable,所以对于时间旅行这种业务不太友好。不过,我们可以自己实现一个具有vuex的简洁语法和immutable属性的redux-x(瞎命名)。 先看一下我们想要的目标是什么样的? 首先, 我们再./models里面定义每个子state树,里面带有namespace、state、reducers、effects等属性, 如下:export default { // 命名空间 namespace: ‘common’, // 初始化state state: { loading: false, }, // reducers 同步更新 类似于vuex的mutations reducers: { updateLoadingStatus(state, action) { return { …state, loading: action.payload } }, }, // reducers 异步更新 类似于vuex的actions efffects: { someEffect(action, store) { // some effect code … … // 将结果返回 return result } }}通过上面的实现,我们基本解决了Redux本身的一些瑕疵1.在effects中存放的方法用于解决不支持异步、副作用的问题 2.通过合并reducer和action, 将模板代码大大减少 3.具有分型结构(namespace),并且中心化处理如何实现暴露的接口redux-x首先,我们只是在外层封装了一层API方便使用,那么说到底,传给redux的combineReducers还是一个redux对象。另外一个则是要处理副作用的话,那就必须使用到了中间件,所以最后我们暴露出来的函数的返回值应该具有上面两个属性,如下:import reduxSimp from ‘../utils/redux-simp’ // 内部实现import common from ‘./common’ // models文件下common的状态管理import user from ‘./user’ // models文件下user的状态管理import rank from ‘./rank’ // models文件下rank的状态管理const reduxX = reduxSimp({ common, user, rank})export default reduxXconst store = createStore( combineReducers(reduxX.reducers), // reducers树 {}, applyMiddleware(reduxX.effectMiddler) // 处理副作用中间件)第一步, 我们先实现一个暴露出来的函数reduxSimp,通过他对model里面各个属性进行加工,大概的代码如下:const reductionReducer = function() { // somecode }const reductionEffects = function() { // somecode }const effectMiddler = function() { // somecode }/** * @param {Object} models /const simplifyRedux = (models) => { // 初始化一个reducers 最后传给combinReducer的值 也是最终还原的redux const reducers = {} // 遍历传入的model const modelArr = Object.keys(models) modelArr.forEach((key) => { const model = models[key] // 还原effect reductionEffects(model) // 还原reducer,同时通过namespace属性处理命名空间 const reducer = reductionReducer(model) reducers[model.namespace] = reducer }) // 返回一个reducers和一个专门处理副作用的中间件 return { reducers, effectMiddler }}还原effects对于effects, 使用的时候如下(没什么区别):props.dispatch({ type: ‘rank/fundRankingList_fetch’, payload: { fundType: props.fundType, returnType: props.returnType, pageNo: fund.pageNo, pageSize: 20 }})还原effects的思路大概就是先将每一个model下的effect收集起来,同时加上命名空间作为前缀,将副作用的key即type 和相对应的方法value分开存放在两个数组里面,然后定义一个中间件,每当有一个dispatch的时候,检查key数组中是否有符合的key,如果有,则调用对应的value数组里面的方法。// 常量 分别存放副作用的key即type 和相对应的方法const effectsKey = []const effectsMethodArr = [] /* * 还原effects的函数 * @param {Object} model /const reductionEffects = (model) => { const { namespace, effects } = model const effectsArr = Object.keys(effects || {}) effectsArr.forEach((effect) => { // 存放对应effect的type和方法 effectsKey.push(namespace + ‘/’ + effect) effectsMethodArr.push(model.effects[effect]) })}/* * 处理effect的中间件 具体参考redux中间件 * @param {Object} store /const effectMiddler = store => next => (action) => { next(action) // 如果存在对应的effect, 调用其方法 const index = effectsKey.indexOf(action.type) if (index > -1) { return effectsMethodArr[index](action, store) } return action}还原reducersreducers的应用也是和原来没有区别:props.dispatch({ type: ‘common/updateLoadingStatus’, payload: true })代码实现的思路就是最后返回一个函数,也就是我们通常写的redux函数,函数内部遍历对应命名空间的reducer,找到匹配的reducer执行后返回结果/* * 还原reducer的函数 * @param {Object} model 传入的model对象 /const reductionReducer = (model) => { const { namespace, reducers } = model const initState = model.state const reducerArr = Object.keys(reducers || {}) // 该函数即redux函数 return (state = initState, action) => { let result = state reducerArr.forEach((reducer) => { // 返回匹配的action if (action.type === ${namespace}/${reducer}) { result = model.reducers[reducer](state, action) } }) return result }}最终代码最终的代码如下,加上了一些错误判断:// 常量 分别存放副作用的key即type 和相对应的方法const effectsKey = []const effectsMethodArr = []/* * 还原reducer的函数 * @param {Object} model 传入的model对象 /const reductionReducer = (model) => { if (typeof model !== ‘object’) { throw Error(‘Model must be object!’) } const { namespace, reducers } = model if (!namespace || typeof namespace !== ‘string’) { throw Error(The namespace must be a defined and non-empty string! It is ${namespace}) } const initState = model.state const reducerArr = Object.keys(reducers || {}) reducerArr.forEach((reducer) => { if (typeof model.reducers[reducer] !== ‘function’) { throw Error(The reducer must be a function! In ${namespace}) } }) // 该函数即redux函数 return (state = initState, action) => { let result = state reducerArr.forEach((reducer) => { // 返回匹配的action if (action.type === ${namespace}/${reducer}) { result = model.reducers[reducer](state, action) } }) return result }}/* * 还原effects的函数 * @param {Object} model /const reductionEffects = (model) => { const { namespace, effects } = model const effectsArr = Object.keys(effects || {}) effectsArr.forEach((effect) => { if (typeof model.effects[effect] !== ‘function’) { throw Error(The effect must be a function! In ${namespace}) } }) effectsArr.forEach((effect) => { // 存放对应effect的type和方法 effectsKey.push(namespace + ‘/’ + effect) effectsMethodArr.push(model.effects[effect]) })}/* * 处理effect的中间件 具体参考redux中间件 * @param {Object} store /const effectMiddler = store => next => (action) => { next(action) // 如果存在对应的effect, 调用其方法 const index = effectsKey.indexOf(action.type) if (index > -1) { return effectsMethodArr[index](action, store) } return action}/* * @param {Object} models */const simplifyRedux = (models) => { if (typeof models !== ‘object’) { throw Error(‘Models must be object!’) } // 初始化一个reducers 最后传给combinReducer的值 也是最终还原的redux const reducers = {} // 遍历传入的model const modelArr = Object.keys(models) modelArr.forEach((key) => { const model = models[key] // 还原effect reductionEffects(model) // 还原reducer,同时通过namespace属性处理命名空间 const reducer = reductionReducer(model) reducers[model.namespace] = reducer }) // 返回一个reducers和一个专门处理副作用的中间件 return { reducers, effectMiddler }}export default simplifyRedux思考如何结合Immutable.js使用?