• Redux进阶(像VUEX一样使用Redux)


    更好的阅度体验

    • 前言
    • redux的问题
    • 方案目标
    • 如何实现
    • 思考

    前言

    Redux是一个非常实用的状态管理库,对于大多数使用React库的开发者来说,Redux都是会接触到的。在使用Redux享受其带来的便利的同时, 我们也深受其问题的困扰。

    redux的问题

    之前在另外一篇文章Redux基础中,就有提到以下这些问题

    • 纯净。Redux只支持同步,让状态可预测,方便测试。 但不处理异步、副作用的情况,而把这个丢给了其他中间件,诸如redux-thunk edux-promise edux-saga等等,选择多也容易造成混乱~
    • 啰嗦。那么写过Redux的人,都知道action educer以及你的业务代码非常啰嗦,模板代码非常多。但是~,这也是为了让数据的流动清晰明了。
    • 性能。粗暴地、级联式刷新视图(使用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 reduxX
    
    const 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
    }
    

    还原reducers

    reducers的应用也是和原来没有区别:

    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使用?

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=368b4t649o8wg

  • 相关阅读:
    课程个人总结
    构建之法阅读笔记06
    构建之法读后感5
    第五周进度条
    提高自身能力
    活动图与状态机图
    对分析业务模型----类图的学习与认识
    需求分析工作的基本道理
    问题账户需求分析
    2016秋季个人阅读计划
  • 原文地址:https://www.cnblogs.com/Darlietoothpaste/p/10266572.html
Copyright © 2020-2023  润新知