Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

源码分析——Redux


Redux 是一个可预测且可维护的全局状态管理的 JS 库。可预测的特性主要建立在单项数据流和不可变性的基础上。

Redux 的单向数据流方式为:Store - View - Action - Store。从这种流向的处理上,循环链表有一些像。

Redux 的使用首先要创建 Store,之后 UI 界面会触发 Action 更新 Store,Store 更新后 View 随之发生变化。

全局状态管理主要解决了 React 中跨组件共享状态时候遇到的复杂性。出了降低复杂性外,还可以增加项目的可维护性,实现关注点分离。

Redux 的可预测性是基于 Immutability(不可变性)。不可变性意味着普通数据类型的 const 初始赋值后就不能在被赋值。但是在 JS 中通过 const 初始化的数组和对象并不算事严格意义上的不可变。因为 const 初始化的数组和对象,是可以改变某个索引或某个 key 的值的。

基于这样的原因,Redux 推荐在处理数组和对象的时候,要返回新的对象拷贝。也就是在实际使用时候经常见到的解构操作。

在 Redux 中的核心概念: - Store - Actions - Reducers

Store 通过 createStore 方法创建。Actions 类似一种事件,类似我们给 Dom 元素上添加的 click 类型的事件。Reducers 接收 store 和 action 对象,生成新的 store。

Actions 是一个普通 JS 对象:

const action = {
  type: 'add',
  payload: 1
}
这里假设在描述加 1 的操作。

Reducer 接收上面的 action,Reducer 的声明形式如:(state, action) => store

下面先创建 createStore 方法,在创建之前要先看一下如何使用 createStore,用法如下:

const initialState = {value: 0}
const reducer1 = (state = initialState, action) => {
  switch(action.type == 'add') {
    return {
      value: state.value + action.payload
    }
  }
  return state
}
// 创建 store 对象
const store = createStore(reducer1)
// 订阅更新
store.subscribe(() => console.log(store.getState()))
// 触发更新
store.dispatch(customAction)

从 createStore 的用法可以看出,数据的更新是通过发布订阅模式实现的。同时 store 对象上会挂在 subscribe 和 dispatch 方法。而且还提供了 getState 方法。

function createStore(reducer) {
  return {
    subscribe,
    dispatch,
    getState
  }
}

我们可以先把返回的对象定义出来,然后在反推如何实现 subscribe、dispatch、getState。

首先增加 getState 方法,这个方法返回当前的 store

function createStore(reducer) {
  let currentState = null
  const getState = () => currentState
  return {
    subscribe,
    dispatch,
    getState
  }
}

在执行 const store = createStore(reducer1) 的时候,只传入了 reducer1 作为参数,所以在 createStore 的实现里面需要能根据传入的 reducer1 计算出 currentState

为了统一执行逻辑,我们可以通过 dispatch 一个内置的 action

function createStore(reducer) {
  let currentState = null
  let currentReducer = reducer

  const dispatch = (action) => {
    currentState = currentReducer(currentState, action)
  }
  const getState = () => currentState

  dispatch({type: 'INIT'})
  return {
    subscribe,
    dispatch,
    getState
  }
}

当 createStore 执行的时候,dispatch 执行后,会调用 reducer1 返回 initialState,也就是 {value: 0},所以 currentState 就是这个值了。

接下来实现 subscribe 和 dispatch

let listenerIdCounter = 0
function createStore(reducer) {
  let currentState = null
  let currentReducer = reducer
  let listeners = new Map()

  const dispatch = (action) => {
    currentState = currentReducer(currentState, action)
    listeners.forEach((fn) => {
      fn(currentState)
    })
  }
  const subscribe = (fn) => {
    const listenerId = listenerIdCounter++
    listeners.set(listenerId, fn)
    return () => {
      listeners.delete(listenerId)
    }
  }
  const getState = () => currentState

  dispatch({type: 'INIT'})
  return {
    subscribe,
    dispatch,
    getState
  }
}
这里采用了 Map 存储订阅的事件,并且通过全局的计数器作为索引。

基础版本的 Redux 已经实现了。接下来要增加 Redux 的扩展性。Redux 在 createStore 方法中设置了三个参数,最后一个参数是 enhancer 。通过传入这个参数,可以对 createStore 和 dispatch 进行拦截或包装。

先看如何使用 enhancer

// enhancer:接收 createStore,返回新的 createStore
const loggerEnhancer = (createStore) => (reducer) => {
  const store = createStore(reducer)
  const rawDispatch = store.dispatch
  return {
    ...store,
    // 改变了 dispatch 的行为
    dispatch(action) {
      console.log('dispatch:', action)
      const action = rawDispatch(action)
      console.log('next state:', store.getState())
      return action
    }
  }
}

上面的 loggerEnhancer 是对 dispatch 做了拦截。在原始 dispatch 前后增加了日志功能。

通过 loggerEnhancer 的实现,很容易写出 createStore 中 enhancer 的实现逻辑

let listenerIdCounter = 0
function createStore(reducer, enhancer) {
  let currentState = null
  let currentReducer = reducer
  let listeners = new Map()

  if(enhancer) {
    return enhancer(createStore)(reducer)
  }

  const dispatch = (action) => {
    currentState = currentReducer(currentState, action)
    listeners.forEach((fn) => {
      fn(currentState)
    })
  }
  const subscribe = (fn) => {
    const listenerId = listenerIdCounter++
    listeners.set(listenerId, fn)
    return () => {
      listeners.delete(listenerId)
    }
  }
  const getState = () => currentState

  dispatch({type: 'INIT'})
  return {
    subscribe,
    dispatch,
    getState
  }
}

enhancer 有明显的 AOP 面向切面编程风格,但是更严谨一些的话,enhancer 是高阶函数或函数装饰的一种模式。

函数装饰相当于用高阶函数给含有函数套壳,在不该动原实现的前提下,扩展前后逻辑或改变行为。

而且,函数装饰可以像俄罗斯套娃,一层套一层的叠加。所以可以实现多 enhancer 。通过函数装饰的方式实现,可以做到不侵入函数代码,易于测试、可复用、可撤销(换回未装饰的函数)