解密Redux: 从源码开始

970 阅读8分钟

Redux是当今比较流行的状态管理库,它不依赖于任何的框架,并且配合着react-redux的使用,Redux在很多公司的React项目中起到了举足轻重的作用。接下来笔者就从源码中探寻Redux是如何实现的。

注意:本文不去过多的讲解Redux的使用方法,更多的使用方法和最佳实践请移步Redux官网

源码之前

基础概念

随着我们项目的复杂,项目中的状态就变得难以维护起来,这些状态在什么时候,处于什么原因,怎样变化的我们就很难去控制。因此我们考虑在项目中引入诸如Redux、Mobx这样的状态管理工具。

Redux其实很简单,可以简单理解为一个约束了特定规则并且包括了一些特殊概念的的发布订阅器。

在Redux中,我们用一个store来管理一个一个的state。当我们想要去修改一个state的时候,我们需要去发起一个action,这个action告诉Redux发生了哪个动作,但是action不能够去直接修改store里头的state,他需要借助reducer来描述这个行为,reducer接受state和action,来返回新的state。

三大原则

在Redux中有三大原则:

  • 单一数据源:所有的state都存储在一个对象中,并且这个对象只存在于唯一的store中;
  • state只读性:唯一改变state的方法就是去触发一个action,action用来描述发生了哪个行为;
  • 使用纯函数来执行修改:reducer描述了action如何去修改state,reducer必须是一个纯函数,同样的输入必须有同样的输出;

剖析源码

项目结构

项目结构

抛去一些项目的配置文件和其他,Redux的源码其实很少很简单:

  • index.js:入口文件,导出另外几个核心函数;
  • createStore.js:store相关的核心代码逻辑,本质是一个发布订阅器;
  • combineReducers.js:用来合并多个reducer到一个root reducer的相关逻辑;
  • bindActionCreators.js:用来自动dispatch的一个方法;
  • applyMiddleware.js:用来处理使用的中间件;
  • compose.js:导出一个通过从右到左组合参数函数获得的函数;
  • utils:两个个工具函数和一个系统注册的actionType;

从createStore来讲一个store的创建

首先我们先通过createStore函数的入参和返回值来简要理解它的功能:

export default function createStore(reducer, preloadedState, enhancer) {

  // ...

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
  }
}

createStore接受三个参数:

  • reducer:用来描述action如何改变state的方法,它给定当前state和要处理的action,返回下一个state;
  • preloadedState:顾名思义就是初始化的state;
  • enhancer:可以直译为增强器,用它来增强store的第三方功能,Redux附带的唯一store增强器是applyMiddleware

createStore返回一个对象,对象中包含使用store的基本函数:

  • dispatch:用于action的分发;
  • subscribe:订阅器,他将会在每次action被dispatch的时候调用;
  • getState:获取store中的state值;
  • replaceReducer:替换reducer的相关逻辑;

接下来我们来看看createStore的核心逻辑,这里我省略了一些简单的警告和判断逻辑:

export default function createStore(reducer, preloadedState, enhancer) {
  // 判断是不是传入了过多的enhancer
  // ...

  // 如果不传入preloadedState只传入enhancer可以写成,const store = createStore(reducers, enhancer)
  // ...

  // 通过在增强器传入createStore来增强store的基本功能,其他传入的参数作为返回的高阶函数参数传入;
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 闭包内的变量;
  // state作为内部变量不对外暴露,保持“只读”性,仅通过reducer去修改
  let currentReducer = reducer
  let currentState = preloadedState
  // 确保我们所操作的listener列表不是原始的listener列表,仅是他的一个副本;
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 确保我们所操作的listener列表不是原始的listener列表,仅是他的一个副本;
  // 只有在dispatch的时候,才会去将currentListeners和nextListeners更新成一个;
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 通过闭包返回了state,state仅可以通过此方法访问;
  function getState() {
    // 判断当前是否在dispatch过程中
    // ...

    return currentState
  }

  // Redux内部的发布订阅器
  function subscribe(listener) {
    // 判断listener的合法性
    // ...

    // 判断当前是否在dispatch过程中
    // ...

    let isSubscribed = true

    // 复制一份当前的listener副本
    // 操作的都是副本而不是源数据
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      // 判断当前是否在dispatch过程中
      // ...

      isSubscribed = false

      ensureCanMutateNextListeners()

      // 根据当前listener的索引从listener数组中删除来实现取掉订阅;
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    // 判断action是不是一个普通对象;
    // ...

    // 判断action的type是否合法
    // ...

    // 判断当前是否在dispatch过程中
    // ...

    try {
      isDispatching = true
      // 根据要触发的action, 通过reducer来更新当前的state;
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 通知listener执行对应的操作;
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替换reducer,修改state变化的逻辑
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer

    // 此操作对ActionTypes.INIT具有类似的效果。
    // 新旧rootReducer中存在的任何reducer都将收到先前的状态。
    // 这有效地使用来自旧状态树的任何相关数据填充新状态树。
    dispatch({ type: ActionTypes.REPLACE })
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      // 任何对象都可以被用作observer,observer对象应该有一个next方法
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        // 返回一个带有unsubscribe方法的对象可以被用来在store中取消订阅
        return { unsubscribe }
      },

      [?observable]() {
        return this
      }
    }
  }

  // 创建store时,将调度“INIT”操作,以便每个reducer返回其初始状态,以便state的初始化。
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
  }
}

从combineReducers谈store的唯一性

仅靠上面的createStore其实已经可以完成一个简单的状态管理了,但是随着业务体量的增大,state、action、reducer也会随之增大,我们不可能把所有的东西都塞到一个reducer里,最好是划分成不同的reducer来处理不同模块的业务。

但是也不能创建多个store维护各自的reducer,这就违背了Redux的单一store原则。为此,Redux提供了combineReducers让我们将按照业务模块划分的reducer合成一个rootReducer。

接下来我们看看combineReducers的源码,这里也是去掉了一些错误警告的代码和一些错误处理方法:

export default function combineReducers(reducers) {
  // 取出所有的reducer遍历合并到一个对象中
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 判断未匹配的refucer
    // ...

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

   // 错误处理的一些逻辑
   // ...

  return function combination(state = {}, action) {

    // 错误处理的一些逻辑
    // ...

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // 对应的reducer
      const reducer = finalReducers[key]
      // 根据指定的reducer找到对应的state
      const previousStateForKey = state[key]
      // 执行reducer, 返回当前state
      const nextStateForKey = reducer(previousStateForKey, action)
      // nextStateForKey undefined的一些判断
      // ...

      // 整合每一个reducer对应的state
      nextState[key] = nextStateForKey
      // 判断新的state是不是同一引用, 以检验reducer是不是纯函数
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

其实到这里可以简单的看出combineReducers就是把多个reducer拉伸展开到到一个对象里,同样也把每一个reducer里的state拉伸到一个对象里。

从bindActionCreators谈如何自动dispatch

现有的store每一次state的更新都需要手动的dispatch每一个action,而我们其实更需要的是自动的dispatch所有的action。这里就用到了bindActionCreators方法。

现在我们来看看bindActionCreators的源码

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 返回绑定了this的actionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  // actionCreators类型判断的错误处理
  // ...

  // 为每一个actionCreator绑定this
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

其实我们在react项目中对这个方法是几乎无感知的,因为是在react-redux的connect中调用了这个方法来实现自动dispatch action的,不然需要手动去dispatch一个个action。

从compose谈函数组合

compose是Redux导出的一个方法,这方法就是利用了函数式的思想对函数进行组合:

// 通过从右到左组合参数函数获得的函数。例如,compose(f, g, h)与do(...args)=> f(g(h(... args)))相同。
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

从applyMiddleware谈如何自定义dispatch

我们的action会出现同步的场景,当然也会出现异步的场景,在这两种场景下dispacth的执行时机是不同的,在Redux中,可以使用middleware来对dispatch进行改造,下面我们来看看applyMiddleware的实现:

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 通过从右到左组合参数函数获得的函数。例如,compose(f, g, h)与do(...args)=> f(g(h(... args)))相同。
    // 对dispatch改造
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

结语

到此,Redux源码的部分就分析完了,但是在具体和React结合的时候还需要用到react-redux,下一篇文章,我将深入到react-redux的源码学习,来探索,在react中,我们如何去使用Redux。