为什么会有这个idea?
-
一个
reducer需要太多样板式代码,你创建相关的actions,constants,reducers或许更多目录,并管理它们,这导致每新增一个reducer都需要往这些文件夹中添加新的reducer相关定义 -
普遍的
reducer,if,else if,else,switch,丑陋且语义不够 -
不想学习
redux toolkit,正好想学习redux and react-redux -
当
reducer随着时间增加,每一次触发reducer,都会触发无关的reducer的default,我认为这些无关的default返回值是无用的,只是在初始化reduxstate时有用,非初始化时这些default的返回值已在redux中保存了一份
如何解决1,2问题?
// 伪代码
const count = new CreateReducer(1).addAction('add',(state,action)=>{
return state + action.count
})
const store = createStore(combineReducers({ count: count.finish() }))
store.getState() // { count: 1 }
// 该有类型提示
count.dispatcher('add',{ count: 1 })
store.getState() // { count: 2 }
我们应该实现一个CreateReducer class
他能保存所有actionHandler,并且能返回一个reducer,同时还能在dispatcher时更新状态
export type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S
actionHandler应该接受一个actionType和一个接受state和actionPayload并返回state的handler
并保存在一个reducerContainer内
// actionType (state,actionPayload) => state
const count = new CreateReducer(1).addAction('add',(state,action)=>{
// 必须返回和state类型相同的value
return state + action.count
})
export class CreateReducer<S, A> {
private reducerObj: object
private state: S
constructor(state: S) {
this.state = state // reducer 默认state,redux初始化调用该reducer时会传入undefined,此时state就会是我们定义的默认值
this.reducerObj = {}
}
addAction(action: L, handler: (state: S, action: Partial<Action & A>) => S): this {
// 如果reduxObj中存在相关action的handler则报错,否则将相关handler保存至容器,在reducer调用时取出
if (Reflect.has(this.reducerObj, action)) throw new Error('The action exists')
this.actionList.push(action)
Reflect.set(this.reducerObj, action, handler)
return this
}
finish(): Reducer<S, Action & A> {
// 返回一个reducer
// 该reducer会在reducerObj内找到actionType相同的handler,并调用返回成为新的state
// 如果没有找到就返回传入的state, 初始化时redux的state为undefine,所以返回的state = this.state
const ret = (State: S = this.state, action: Action & A) =>
const fn = Reflect.get(this.reducerObj, action.type)
if (typeof fn === 'function') return fn.call(null, State, action)
return State
}
return ret
}
}
如果你的reducer被combineReducers包裹,那么你的reducer会在这里得到调用
现在我们完成了reducer的生成,那该怎么触发更新呢?这是一个难题,我们应该怎么在class里面获得redux的dispatch函数并触发更新?
或许我们可以破坏reducer的定义...
export type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A, dispatch?: Dispatch<A>) => S
我们在redux调用reducer时多传入一个dispatch函数进去
在redux createStore内部改造后的reducer调用
currentState = currentReducer(currentState, action, dispatch as Dispatch)
在redux combineReducers内部改造后的reducer调用
// previousStateForKey 为state 初始化时候为空
// action 初始化时为redux内部的常量,用于获取default值
// dispatch 我们传入的dispatchHandler
const nextStateForKey = reducer(previousStateForKey, action, dispatch)
改造一下我们的CreateReducer
type stateHandlerType<S, A> = (state: S) => A
export class CreateReducer<S, A> {
private reducerObj: object
private state: S
private dispatch: Dispatch<Action & A> | undefined
constructor(state: S) {
this.state = state
this.reducerObj = {}
}
addAction(action: L, handler: (state: S, action: Partial<Action & A>) => S): this {
if (Reflect.has(this.reducerObj, action)) throw new Error('The action exists')
Reflect.set(this.reducerObj, action, handler)
return this
}
private setDispatch(dispatch: Dispatch<Action & A>) {
this.dispatch = dispatch
}
finish(): Reducer<S, Action & A> {
const ret = (State: S = this.state, action: Action & A, dispatch?: Dispatch<Action & A>) => {
// dispatch为我们传入的redux内部的dispatch function
// 保存起来在dispatcher函数中使用
dispatch && this.setDispatch(dispatch)
const fn = Reflect.get(this.reducerObj, action.type)
if (typeof fn === 'function') return fn.call(null, State, action)
return State
}
return ret
}
// actions actionType
// states 也就是你传入的action data
dispatcher(actions: L, states?: Partial<A> | stateHandlerType<S, Partial<A>>): void {
if (!this.dispatch) {
throw new Error('dispatch does not exist')
}
if (!Reflect.has(this.reducerObj, actions)) {
throw new Error(`Did you add the ${actions} action?`)
}
let stateHandler: undefined | A
if (typeof states === 'function')
stateHandler = states(this.state)
else stateHandler = states
// 通过redux提供的 bindActionCreators 函数传入action和dispatch 来帮助更新
bindActionCreators(() => ({ type: actions, ...stateHandler }), this.dispatch as any)()
}
}
// 完整的调用方式,带上泛型,当然如果不带也会被自动推导出来
const count = new CreateReducer<number,{ count: number }>(1).addAction('add',(state,action)=>{
return state + action.count
})
const store = createStore(combineReducers({ count: count.finish() }))
store.getState() // { count: 1 }
// 该有类型提示
count.dispatcher('add',{ count: 1 })
store.getState() // { count: 2 }
解决重复更新
当你使用combineReducers时你的每一个reducer在更新时都会被循环传入action调用
为什么会被循环调用?
在初始化时,传入redux内部指定的actions,命中你的reducer default分支获取默认值,并保存在redux的中,这没有问题
但问题是当你的reducer变多时,每一次更新都会命中一些无用reducer的default分支,除非你的default分支会有一些计算逻辑,否则default命中就是多余的
如何解决
首先我们可以将所有的actionType保存在reducer中,形成一个数组
并在reducer调用时判断传入的action是否在此次的reducer actionTypeList中
如果本次的actionType与此次reducer的更新无关可直接跳过
所以我们又要改造一下我们的CreateReducer class
type stateHandlerType<S, A> = (state: S) => A
export class CreateReducer<S, A, L extends string> {
private reducerObj: object
private state: S
private dispatch: Dispatch<Action & A> | undefined
private actionList: string[]
constructor(state: S) {
this.state = state
this.reducerObj = {}
this.actionList = []
}
addAction(action: L, handler: (state: S, action: Partial<Action & A>) => S): this {
if (Reflect.has(this.reducerObj, action)) throw new Error('The action exists')
// 将传入的action保存到actionList中
this.actionList.push(action)
Reflect.set(this.reducerObj, action, handler)
return this
}
// 获取actionList
getActionList() {
return this.actionList
}
private setDispatch(dispatch: Dispatch<Action & A>) {
this.dispatch = dispatch
}
finish(): Reducer<S, Action & A> {
const ret = (State: S = this.state, action: Action & A, dispatch?: Dispatch<Action & A>) => {
dispatch && this.setDispatch(dispatch)
const fn = Reflect.get(this.reducerObj, action.type)
if (typeof fn === 'function') return fn.call(null, State, action)
return State
}
// 在reducer的prototype中添加一条关于actionList的属性,保存了该reducer的所有action
ret.prototype.getActionList = this.getActionList()
return ret
}
dispatcher(actions: L, states?: Partial<A> | stateHandlerType<S, Partial<A>>): void {
if (!this.dispatch) {
throw new Error('dispatch does not exist')
}
if (!Reflect.has(this.reducerObj, actions)) {
throw new Error(`Did you add the ${actions} action?`)
}
let stateHandler: undefined | A
if (typeof states === 'function')
stateHandler = states(this.state)
else stateHandler = states
bindActionCreators(() => ({ type: actions, ...stateHandler }), this.dispatch as any)()
}
}
并在combineReducers函数中添加一段代码
// 是否是第一次初始化
let mount = true
在combineReducers 的返回的reducer中添加这样一段逻辑
for (let i = 0; i < finalReducerKeys.length; i++) {
// 获取每个reducer的key
const key = finalReducerKeys[i]
// 在reducerlist中找出对应的reducer handler
const reducer = finalReducers[key]
if (!mount) {
// 在非初始化时才会进入的逻辑
// 当前action不存在该reducer的所有action中, 表示该reducer与这次update无关,直接跳过
if (!(reducer.prototype.getActionList as string[]).includes(action.type)) continue
}
// 找出该reducer对应的state
const previousStateForKey = state[key]
// 带上该state 调用reducer
const nextStateForKey = reducer(previousStateForKey, action, dispatch)
// 再次判断reducer的返回值
if (typeof nextStateForKey === 'undefined') {
const actionType = action && action.type
throw new Error(
`When called with an action of type ${actionType ? `"${String(actionType)}"` : '(unknown type)'}, the slice reducer for key "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
// 初始化对应reducer的value
nextState[key] = nextStateForKey
// 通过判断当前reducer的返回值是否等于传入的state判断是否change
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
mount = false
大功告成
源码地址 内含redux的关键逻辑注释