reducer是redux的三个核心概念之一,它指定了应用状态的变化如何响应 actions 并发送到 store,需要由开发人员自己定义,提起reducer,最常想到的一个准则是reducer要是纯函数,那么这其中是什么原因呢,如果不是纯函数的话会导致redux不可用吗?接下来就来一步步分析下这个问题
reducer响应actions的原理
我们定义的reducer负责接收action,并返回一个新的state,但在react组件开发中,我们也仅仅定义了而从未实际调用过reducer,所有reducer作用的原理是什么呢?
实际上,组件中我们会手动调用dispatch方法发送action来响应事件,而这里的发送action不止发送action那么简单,还包括了调用reducer,更新状态为reducer的处理结果,触发订阅事件等一系列操作,都是在dispatch的方法内实现的,到这里可以来通过源码的处理流程来了解一些这个过程~
function dispatch(action) {
...
try {
//将flag置为true,表明处于分发逻辑中
isDispatching = true
//currentReducer即为传入的reducer函数,这里会自动调用currentReducer函数,并将返回值赋给currentState
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//调用订阅函数
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
可以看到,dispatch处理中reducer方法的调用无疑是很关键的一步,经过它的处理之后才生成了新的状态数据 在redux中,保持reducer是一个纯函数非常重要,保证reducer是纯函数要达到以下几点:
- 不得修改传入的参数
- 不得调用非纯函数,如Date.now()
- 不得执行有副作用的操作,如API请求和路由跳转
那么为什么reducer函数要遵守这几点规则呢,如果不遵守的话又会怎么样呢
纯函数条件之一:不得修改传入的参数
function reuderfunc(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return ...
default:
return state
}
}
reducer接受的参数由两个,一个是action
,一个是state
,其中action
只是用来传递信息的,完全没有修改的必要,那么state
呢,既然reducer
是用来返回一个新的state
的,在这个过程中直接基于传入的state
来改,然后再返回,貌似也可以达到效果?
实际上,虽然在reducer处理过程中改变了传入的state,有可能redux还会正常运转,但像时间旅行,录制和回放这类依赖于历史状态的功能则无法实现了,要知道这可是redux当时的设计初衷之一~
特别注意的是,如果改变了传入的参数initState,或该对象底层的任意一个key值,都有可能在应用于react组件时,导致react任为该状态无变化,而不更新组件,因为redux源代码中将oldState和newState(reducer返回的结果)做比较,如果某一级state指定的引用相等,则会导致此结果,这样做是牺牲一点计算性能(生成新对象)来保证页面刷新。
纯函数条件之二:不得调用非纯函数,如 Date.now() 或 Math.random()
redux的核心提供可预测化的状态管理,即无论何时特定的action触发的行为永远保持一致,试想如果reducer中有Date.now()等非纯函数,即使同样的action,那么reducer处理过程中也是有所不同的,不再能保证可预测性
纯函数条件之二:执行有副作用的操作
首先,执行有副作用的操作,如api和路由跳转,因为设置到后台的处理,会带来和上一节同样的问题,同样的action触发后的处理过程可能有所不同(依据后台处理或路由跳转而定),失去了可预测性 其次因为reducer函数的返回值是要作为下一个状态值被返回的,那么试想当reducer中有api调用时,api是会向后台请求数据的异步函数,往往希望后台的请求结果数据会应用于新的state,但是这时候会发现,这个异步函数卸载reducer中没有办法影响到state的更新,因为在异步请求处理完成时,reducer函数已经被返回(函数的返回是同步的),所以说在redcuer中调用api也完全没有意义了.
api请求该如何执行
刚才提到在reducer中不能执行api请求操作,但是很明显api操作是不可避免的,因为总要向后台请求数据,那么api请求应该如何做呢?这里有两个办法
- 在dispatch方法之前进行api请求:在dispatch之外先进行api异步请求,当收到请求结果后,根据结果的不同选择dispatch不同的action
- 应用redux-thunk,redux-promise等中间件,就可以在dispatch函数中直接执行api请求等异步操作了。