Reducer 为什么必须是纯函数?

5,946 阅读3分钟

前言

本文不讲 react、redux、action、reducer 的具体应用,只是单纯的讲解 reducer 为什么必须是纯函数,本文适合有一定 redux 开发经验的同学。

用过 react 的同学对 redux 一定不会陌生,我们知道 redux 可以提供可预测化的状态管理。在一个应用中,所有的 state 都是以一个对象树的形式存在一个单一的 store 中,唯一改变 state 的办法就是触发 action,而 reducer 就是用来编写专门的函数决定每个 action 如何改变应用的 state 。

如官网所说,reducer 就是一个纯函数,接受旧的 state 和 action,返回新的 state (previousState, action) => newState

我们的疑问来了:

为什么reducer 必须是一个纯函数?
为什么必须返回一个新的 state?
返回旧的 state 为什么不行?
redux 源码是怎么写的?

纯函数写法

我们首先来看下正常案例,我们一般会有如下几种写法返回新state:

  1. 直接返回一个新对象

  1. 使用 Object.assign()返回一个新对象

  1. 使用 Immutable.js 返回一个新对象

在 Redux store 中保存了 reducer 返回的 这个 state,这个新的 store 树就是应用的下一个 state, 所有订阅 store.subscribe(listener)的监听器都将被调用,监听器里可以调用 store.getState()获得当前 state 此时,我们就可以使用新的 state 来更新 UI setState(newState)

什么是纯函数?

这里简单的讲解一下什么是纯函数?

  1. 相同的输入永远返回相同的输出
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用 

为什么reducer必须为纯函数?

我们先来试验下,如果 reducer 不是纯函数会发生什么? 我们将上面代码reducer改造一下,直接修改 state,而不是返回新的 state

改变代码后,我们发现当我们触发了 action 以后,页面没有发生任何变化,这是为什么呢?

我们来看下 redux 源码:

通过源代码,我们发现,var nextStateForKey = reducer(previousStateForKey, action), nextStateForKey就是通过 reducer 执行后返回的结果(state),然后通过hasChanged = hasChanged || nextStateForKey !== previousStateForKey来比较新旧两个对象是否一致,此比较法,比较的是两个对象的存储位置,也就是浅比较法,所以,当我们 reducer 直接返回旧的 state 对象时,Redux 认为没有任何改变,从而导致页面没有更新。

为什么 Redux 会这样设计?

因为比较两个 javascript 对象中所有的属性是否完全相同,唯一的办法就是深比较,然而,深比较在真实的应用中代码是非常大的,非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回就的对象,这也就是 redux 为什么要把 reducer 设计成纯函数的原因。