共计 5564 个字符,预计需要花费 14 分钟才能阅读完成。
前言
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 使用?