zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    129. Sum Root to Leaf Numbers
    117. Populating Next Right Pointers in Each Node II
  • 原文地址:https://www.cnblogs.com/Darlietoothpaste/p/10266572.html
Copyright © 2011-2022 走看看