资讯专栏INFORMATION COLUMN

React-Redux进阶(像VUEX一样使用Redux)

levius / 2606人阅读

摘要:前言是一个非常实用的状态管理库,对于大多数使用库的开发者来说,都是会接触到的。在使用享受其带来的便利的同时,我们也深受其问题的困扰。只支持同步,让状态可预测,方便测试。粗暴地级联式刷新视图使用优化。

前言

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 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://www.ucloud.cn/yun/101587.html

相关文章

  • Vuex、Flux、ReduxRedux-saga、Dva、MobX

    摘要:也就是说不应该有公开的,所有都应该是私有的,只能有公开的。允许使用方法设置监听函数,一旦发生变化,就自动执行这个函数。用一个叫做的纯函数来处理事件。可以通过得到当前状态。在中,同步的表现就是发出以后,立即算出。 这篇文章试着聊明白这一堆看起来挺复杂的东西。在聊之前,大家要始终记得一句话:一切前端概念,都是纸老虎。 不管是Vue,还是 React,都需要管理状态(state),比如组件之...

    hiYoHoo 评论0 收藏0
  • 骚操作!在react中使用vuex

    摘要:原文地址前言笔者最近在学习使用,提到就绕不过去。同时应当注意,当组件时应当重新收集依赖,因为之后依赖关系很可能已经变化了清空依赖至此,我们的小目标已经完成了,在中使用不再是梦 原文地址 前言 笔者最近在学习使用react,提到react就绕不过去redux。redux是一个状态管理架构,被广泛用于react项目中,但是redux并不是专为react而生,两者还需要react-redux...

    OnlyLing 评论0 收藏0
  • 【React进阶系列】手写实现react-redux api

    简介:简单实现react-redux基础api react-redux api回顾 把store放在context里,所有子组件可以直接拿到store数据 使组件层级中的 connect() 方法都能够获得 Redux store 根组件应该嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...

    刘玉平 评论0 收藏0
  • React-redux基础

    摘要:简介创建的函数,返回一个对象,包含等方法合并多个中间件处理,在实际的前调用一系列中间件,类似于绑定和函数式编程中常见的方法,介绍官方提供的绑定库。 前言 在学习了React之后, 紧跟着而来的就是Redux了~ 在系统性的学习一个东西的时候, 了解其背景、设计以及解决了什么问题都是非常必要的。接下来记录的是, 我个人在学习Redux时的一些杂七杂八~ Redux是什么 通俗理解 h...

    jsyzchen 评论0 收藏0
  • React-redux进阶之Immutable.js

    摘要:的优势保证不可变每次通过操作的对象都会返回一个新的对象丰富的性能好通过字典树对数据结构的共享的问题与原生交互不友好通过生成的对象在操作上与原生不同,如访问属性,。 Immutable.js Immutable的优势 1. 保证不可变(每次通过Immutable.js操作的对象都会返回一个新的对象) 2. 丰富的API 3. 性能好 (通过字典树对数据结构的共享) Immutab...

    孙淑建 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<