为什么会有这个idea
?
-
一个
reducer
需要太多样板式代码,你创建相关的actions
,constants
,reducers
或许更多目录,并管理它们,这导致每新增一个reducer
都需要往这些文件夹中添加新的reducer
相关定义 -
普遍的
reducer
,if
,else if
,else
,switch
,丑陋且语义不够 -
不想学习
redux toolkit
,正好想学习redux and react-redux
-
当
reducer
随着时间增加,每一次触发reducer
,都会触发无关的reducer
的default
,我认为这些无关的default
返回值是无用的,只是在初始化redux
state
时有用,非初始化时这些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
的关键逻辑注释