更好的阅度体验

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

前言

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

redux的问题

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

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

Redux进阶(像VUEX一样使用Redux)的更多相关文章

  1. Redux/Mobx/Akita/Vuex对比 - 选择更适合低代码场景的状态管理方案

    近期准备开发一个数据分析 SDK,定位是作为数据中台向外输出数据分析能力的载体,前端的功能表现类似低代码平台的各种拖拉拽.作为中台能力的载体,SDK 未来很大概率会需要支持多种视图层框架,比如Vue2 ...

  2. react+redux教程(七)自定义redux中间件

    今天,我们要讲解的是自定义redux中间件这个知识点.本节内容非常抽象,特别是中间件的定义原理,那多层的函数嵌套和串联,需要极强逻辑思维能力才能完全消化吸收.不过我会多罗嗦几句,所以不用担心. 例子 ...

  3. 【React】360- 完全理解 redux(从零实现一个 redux)

    点击上方"前端自习课"关注,学习起来~ 前言 记得开始接触 react 技术栈的时候,最难理解的地方就是 redux.全是新名词:reducer.store.dispatch.mi ...

  4. React进阶篇(2) -- Redux

    前言 如果还不知道为什么要使用Redux,说明你暂时还不需要它. 三大原则 单一数据源 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一 ...

  5. Redux进阶(一)

    State的不可变化带来的麻烦 在用Redux处理深度复杂的数据时会有一些麻烦.由于js的特性,我们知道当对一个对象进行复制时实际上是复制它的引用,除非你对这个对象进行深度复制.Redux要求你每次你 ...

  6. Redux进阶(Immutable.js)

    更好的阅读体验 更好的阅度体验 Immutable.js Immutable的优势 1. 保证不可变(每次通过Immutable.js操作的对象都会返回一个新的对象) 2. 丰富的API 3. 性能好 ...

  7. redux进阶 --- 中间件和异步操作

    你为什么需要异步操作? https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in ...

  8. (六)Redux进阶

    1 UI组件与容器组件的拆分 UI组件(傻瓜组件):只负责页面显示,没有任何逻辑 容器组件(聪明组件):并不去管UI到底长成什么样,关注的是整个业务逻辑 2 无状态组件 一个普通的函数就是无状态组件 ...

  9. Redux 进阶之 react-redux 和 redux-thunk 的应用

    1. react-redux React-Redux 是 Redux 的官方 React 绑定库. React-Redux 能够使你的React组件从Redux store中读取数据,并且向 stor ...

随机推荐

  1. linux crontab 执行mysqldump全局备份为空

    今天遇到个问题,在定时备份时 去查看备份文件,发现大小竟然为0,执行 备份sh文件备份, 备份的sql文件大小正常.试了几种办法. 最终解决办法: 问题原因: 因为我设置的环境变量 就直接在sh中 使 ...

  2. 压力测试工具 ab

    ab 是Apache 自带的一个压力测试工具,命令行,是 ApacheBench 命令的缩写. ab的原理:ab命令会创建多个并发访问线程,模拟多个访问者同时对某一URL地址进行访问.它的测试目标是基 ...

  3. ElasticSearch(6.2.2)的java API官方文档的总结 (三)

    一 : SearchRequest用于任何与搜索文档,聚合和建议有关的操作,并且还提供了对生成的文档进行高亮显示的方法. 在最基本的形式中,我们可以向请求添加一个查询:    1:添加一个Search ...

  4. Linux系统下如何运行.sh文件

    在Linux系统下运行.sh文件有两种方法,比如我在root目录下有个datelog.sh文件 第一种(这种办法需要用chmod使得文件具备执行条件(x): chmod u+x datelog.sh) ...

  5. FTP出现PORT模式成功, 请更新你的站点配置文件

    最近用FTP连接站点,经常出现连接不上或者连接失败,提示以PASV模式连接失败,正在使用PORT模式连接,最后才能连接成功,连接时间也是相当长,又慢又不稳定.   工具/原料   FlashFXP等F ...

  6. monkey 命令详解

    monkey命令详解   1.  $ adb shell monkey <event-count>                <event-count>是随机发送事件数 例 ...

  7. 【子集或者DFS】部分和问题

    题目: 给定整数序列a1,a2,...,an,判断是否可以从中选出若干数,使它们的和恰好为k.1≤n≤20   -108≤ai≤108   -108≤k≤108 输入: n=4 a={1,2,4,7} ...

  8. [Swift]LeetCode315. 计算右侧小于当前元素的个数 | Count of Smaller Numbers After Self

    You are given an integer array nums and you have to return a new countsarray. The counts array has t ...

  9. Java常用工具类练习题

    1.请根据控制台输入的特定日期格式拆分日期 如:请输入一个日期(格式如:**月**日****年) 经过处理得到:****年**月**日 提示:使用String的方法indexOf.lastIndexO ...

  10. 《关于长沙.NET技术社区未来发展规划》问卷调查结果公布

    那些开发者们对于社区的美好期待 2月,长沙.net 技术社区自从把群拉起来开始,做了一次比较正式.题目为<关于长沙.NET技术社区未来发展规划>的问卷调查,在问卷调查中,溪源写道: 随着互 ...