乐趣区

解密Redux-从源码开始

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。

退出移动版