从 Redux 开始读源码

从Redux开始读源码

前言

虽为一名菜鸟程序员,但是也有读源码的心。由于最近在看Redux,并且Redux的代码量也比较少,遂鼓起勇气,准备一试。

Flux的设计思想

Redux的设计思想很大程度上来源于Flux架构,我们可以认为Redux是Flux的一种实现形式。

Flux并不是一个具体的框架,是由FaceBook技术团队提出的应用架构,这套架构约束的是应用处理数据的模式。在Flux中,一个应用将被拆成一下4个部分。

  1. View(视图):用户界面。 改用户界面是可以以任何形式展示出来的React、Vue、Angular都可以。

  2. Action(动作): 可以理解为视图层发出的’消息’。它会触发应用状态的改变。

  3. Dispatcher(派发器): 处理动作分发,负责对Action进行分发。

  4. Store(数据层): 它是存储应用状态的”仓库“,此外还会定义修改状态的逻辑。store的变化最终会映射到View层上去

    详细的Flux请参考

Redux与Flux

Redux 库和 Flux 架构之间可以说是“你侬我侬”,虽然 Redux 在实现层面并没有按照 Flux 那一套来(比如 Flux 中允许多个 Store 存在,而 Redux 中只有一个 Store 等),但 Redux 在设计思想上确实和 Flux 一脉相承。

前面我们介绍的 Flux 架构的特征、解决问题的思路,包括使用场景方面的注意事项,完全可以迁移到 Redux 上来用。基于 Flux 的思想背景去理解 Redux 这个落地产物,你的学习曲线将会更加平滑一些。

接下来我们在介绍 Redux 的实现原理之前,先简单回顾一下它的关键要素与工作流。Redux 主要由 3 部分组成:Store、Reducer 和 Action。

  1. Store:它是一个单一的数据源,而且是只读的。

  2. Action 人如其名,是“动作”的意思,它是对变化的描述。

  3. Reducer 是一个函数,它负责对变化进行分发和处理,最终将新的数据返回给 Store。

Store、Action 和 Reducer 三者紧密配合,便形成了 Redux 独树一帜的工作流,如下图所示:

图片来源:拉勾教育

开始使用Redux

为了方便之后我们对Redux源码的分析,我们先来体验一下Redux的使用过程。熟悉一下Redux的API

npm install --save redux

在node环境下直接运行改该文件 node index.js

var redux = require('redux');

/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数。
 * 描述了 action 如何把 state 转变成下一个 state。
*/
function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

let store = redux.createStore(counter, 10086);

console.log('getState:', store.getState()) // 10086

// 改变内部 state 唯一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' }); // 10087

// 1. subscribe 是订阅消息,当state发生改变时,会触发sbscribe 
// 2. unSubscribeA 是取消订阅消息,执行了 unSubscribeA() 就不会继续监听了
const unSubscribeA =  store.subscribe(() => {
  console.log('SubscribeA收到了 state发生改变了的消息');
  console.log(store.getState())
});

store.dispatch({ type: 'INCREMENT' }); // 10088

store.dispatch({ type: 'DECREMENT' }); // 10087

unSubscribeA(); // 之后的变化就监听不到了

store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });

console.log('getState:', store.getState()) // 10092

从上面的例子中,我们得到以下信息(注意Redux中的三个关键概念):

  1. reducer的形式为 (state, action) => state 的纯函数。

  2. reducer接收两个参数 1.当前的state 2.action对象

  3. createStore创建的store是存储数据的,并且可以通过getState API拿到当前state

  4. 改变store中数据的唯一方法是通过store的dispatch API 传入一个action对象

  5. 可以调用store的subscribe API来订阅state改变的消息。该API返回的函数可以取消该订阅

Redux源码-Utils

说到Redux源码,其实算是比较容易理解的原因如下: 1. 本身代码量很小,只有2KB 2. 并没有过多的依赖第三方的包,十分便于阅读。整个Redux项目,我们只需关注src下的部分即可。

源码地址

这里主要分成两大块: 1. Utils工具包 2.Redux自身的逻辑

建议从Utils开始阅读,从易到难。

1.Utils

actionTypes.js

const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

这段代码仅仅对外暴露一个ActionTypes的枚举对象,提供了INIT、REPLACE、PROBE_UNKNOWN_ACTION三种。

tips:randomString这个生成随机字符串的方法建议大家学习一下,看看别人是如何生成随机字符串的,核心就是toString()这个方法接受一个2-36的数字作为转化的基数,简单的说就是几进制。

详细信息请移步 Number.prototype.toString()

isPlainObject.js

/**
 * @param {any} obj The object to inspect.
 * @returns {boolean} True if the argument appears to be a plain object.
 */
export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

isPlainObject用来判断一个对象是否是一个js原生对象(通过new Object或者字面量的方式构建出来的对象)。举个🌰:

var test1 = new Object();
var test2 = {name: 'zaoren'};

var test3 = () => {};
class Person{
  name = 'zaoren'
}
var test4 = new Person(); 

[test1, test2, test3, test3].map(item => isPlainObject(item)) // [true true false false]

从代码的角度来理解:原型链的上一层指向null的对象(不知道讲的对不对)。

warning.js

/**
 * Prints a warning in the console if it exists.
 *
 * @param {String} message The warning message.
 * @returns {void}
 */
export default function warning(message) {
  /* eslint-disable no-console */
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  /* eslint-enable no-console */
  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message)
  } catch (e) {} // eslint-disable-line no-empty
}

warning这个工具函数就是用来打印错误信息的。刚开始看的时候有些疑虑,咦?怎的,console这个API难道还会有兼容性问题?一查,果然!IE6以下不支持console操作

Redux源码-逻辑代码

我们先不急着往下看,先来回忆一下,在开始使用Redux中我们在Redux的实践中都用了哪些API:createStore、store.getState、stroe.dispatch、store.subscribe。当然(store, reducer, action)依然是我们Redux最核心的概念。

index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== 'production', warn the user.
 */
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
      `...`
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

index.js文件是Redux库的入口文件,我们和Redux的第一次交手时调用了createStore方法,相信很好理解,其他的API由于篇幅原因没有提供具体的例子(如果你曾经的项目中使用过,而你恰好又有些陌生,建议先打开项目找个特定的场景来回忆一下)

  1. __DO_NOT_USE__ActionTypes: utils中预先声明的三个ActionTypes,为了保证actionType的唯一性。把已经用了的ActionTypes暴露给用户。

  2. isCrushed函数: 从注解中的意思来看,该函数存在的目的是防止用户在非生产环境下对代码进行压缩。比如

压缩前

function isCrushed() {}

压缩后

function d(){}

那么在之后的 if 判断中就会使用utils中的message提示用户,报错。

createStore.js

带注释的createStore.js

首先,我们先查看官方文档createStore是如何使用的。

createStore(reducer, [preloadedState], enhancer)
  1. reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。

  2. [preloadedState] (any): 初始时的 state。从文档上来看preloadedState是一个可有可无的参数(有无第二个参数都不影响createStore对enhancer的处理)

  3. enhancer (Function): Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口,可以直接译作增强器。

返回值: 返回一个对象,有dispatch, subscribe, getState等属性…

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

首先是多传参情况的校验,当第二个参数和第三个参数都为function类型 或者 有第四个参数为function类型时,说明这时候用户可能传入了多个enhancer,抛出异常!

// 多传参数的情况 报错!
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

接下来的的代码是对参数的类型进行判断,来确保我们只传reducer和enhancer变量时,也能对enhancer正确处理。

// createStore函数的 preloadedState不传,enhancer作为第二个参数传入
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

从这行代码可以看出,当我们的传入了enhancer,那么createStore函数返回的内容就移步到enhancer函数内部执行。不过我们可以暂且忽略enhancer,继续往下看creatStore的主流程。

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    
    // 如果有 增强器 就在增强器中返回createStore该返回的内容
    return enhancer(createStore)(reducer, preloadedState)
  }
定义的一些参数
let currentReducer = reducer 
let currentState = preloadedState  //从函数createStore第二个参数preloadedState获得
// 记录下当前正在工作中的 listeners 数组的引用,将它与可能发生改变的 nextListeners 区分开来,以确保监听函数在执行过程中的稳定性
// (为了防止在 订阅函数A 中解绑 订阅函数B 所以需要将nextListeners 与当前正在执行中的 listeners 剥离开来)
let currentListeners = [] //当前订阅者列表 
let nextListeners = currentListeners // 新的订阅者列表
let isDispatching = false // 作为锁来用,我们redux是一个统一管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改

getState

getState用来获获取store中存储的状态。在获取状态前需要判断当前是否正在进行dispatch(对state进行更改的操作),确保返回的state是正确的state.

function getState() {
    // 当在reducer操作的时候,是不可以读取当前的state值的
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    // 1. 由于返回的currentState就是我们当前State的引用
    // 2. 也没有对currentState的set方法做代理 所以对于这个currentState是可以直接修改的
    // 但是直接修改currentState,不是通过dispatch的方式没法通知我们的订阅者更新状态,没意义。
    return currentState
  }
subscribe

subscribe函数为我们提供了订阅store中state状态变化的功能,并且返回一个取消订阅的函数。同样的,在进行dispatch时,也不允许订阅状态。每次订阅前都会先调用ensureCanMutateNextListeners函数将currentListeners拷贝一份存到nextListeners中。并且返回一个取消订阅的函数。

function subscribe(listener) {
    // 判断订阅者是否为函数
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    // 是否有reducer正在进行数据修改
    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
      )
    }

    // 表示该订阅者在订阅状态中
    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // 取消订阅函数
    return function unsubscribe() {
      // 如果已经取消订阅了 直接return
      if (!isSubscribed) {
        return
      }

      // 是否有reducer正在进行数据修改
      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      // 取消订阅后 从订阅者列表中删除
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
function ensureCanMutateNextListeners() {
    // 当nextListeners和currentListeners为同一个引用时,则做一层浅拷贝
    // 这里用的就是Array.prototype.slice方法,该方法会返回一个新的数组,这样就可以达到浅拷贝的效果(// 则将 nextListeners 纠正为一个内容与 currentListeners 一致、但引用不同的新对象)
    if (nextListeners === currentListeners) {
    // 函数dispatch里面将二者合并成一个引用,为啥这里有啥给他俩分开?直接用currentListeners不可以吗?
    // 这里这样做其实也是为了数据的一致性,因为有这么一种的情况存在。当redux在通知所有订阅者的时候,此时又有一个新的订阅者加进来了。
    // 如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序
      nextListeners = currentListeners.slice()
    }
  }

注册监听也是操作 nextListeners,触发订阅也是读取 nextListeners(实际上,取消监听操作的也是 nextListeners 数组)。既然如此,要 currentListeners 有何用?

举个例子,下面这种操作在 Redux 中完全是合法的:

// 定义监听函数 A
function listenerA() {
}
// 订阅 A,并获取 A 的解绑函数
const unSubscribeA = store.subscribe(listenerA)
// 定义监听函数 B
function listenerB() {
  // 在 B 中解绑 A
  unSubscribeA()
}
// 定义监听函数 C
function listenerC() {
}
// 订阅 B
store.subscribe(listenerB)
// 订阅 C
store.subscribe(listenerC)

在这个 Demo 执行完毕后,nextListeners 数组的内容是 A、B、C 3 个 listener:

[listenerA,  listenerB, listenerC]

接下来若调用 dispatch,则会执行下面这段触发订阅的逻辑:

// 触发订阅

const listeners = (currentListeners = nextListeners);

for (let i = 0; i < listeners.length; i++) {

  const listener = listeners[i];

  listener();

}

当 for 循环执行到索引 i = 1 处,也就是对应的 listener 为 listenerB 时,问题就会出现:listenerB 中执行了 unSubscribeA 这个动作。而结合我们前面的分析,监听函数注册、解绑、触发这些动作实际影响的都是 nextListeners。假如说不存在 currentListeners,那么也就意味着不需要 ensureCanMutateNextListeners 这个动作。若没有 ensureCanMutateNextListeners,unsubscribeA() 执行完之后,listenerA 会同时从 listeners 数组和 nextListeners 数组中消失(因为两者指向的是同一个引用),那么 listeners 数组此时只剩下两个元素 listenerB 和 listenerC,变成这样:

[listenerB, listenerC]

再执行listers[2]()就会报错。

dispatch

函数dispatch在函数体一开始就进行了三次条件判断,分别是以下三个:

  • 判断action是否为简单对象
  • 判断action.type是否存在
  • 判断当前是否有执行其他的reducer操作

三个校验都通过后,第一件事情就是先上锁。将isDispatching设为true,告诉其他人,我正在dispatching,为了保证数据的唯一性,请不要做其他操作。然后执行currentReducer函数。执行完之后,通知订阅者,dispatch这个动作发生了(状态不一定发生了改变)。最后,返回当前的action(redux在设计的时候,为什么要把当前的action返回呢,暂时没想明白???)。

function dispatch(action) {
    // 判断action是否为简单对象
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    // 判断action.type是否存在
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    // 判断当前是否有其他的reducer操作
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      // 当前有dispatch正在进行, 上锁!( 防止 currentReducer(currentState, action) 中调用 dispatch 就会死循环)
      isDispatching = true
      // 把 currentState 和 action传给 reducer
      currentState = currentReducer(currentState, action)
    } finally {
      // dispatch结束 解锁!
      isDispatching = false
    }

    // 通知订阅者做数据更新
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    // 为啥要返回当前的action,是为了日志的记录吗?
    return action
  }
replaceReducer
function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
}

这个函数是用来替换reducer的,平时项目里基本很难用到,replaceReducer函数执行前会做一个条件判断:

判断所传reducer是否为函数
通过条件判断之后,将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。

observable

这个observable函数,并没有调用,即便暴露出来我们也办法使用。所以我们就跳过这块,如果有兴趣的话,可以去作者给的github的地址了解一下

/**
 * Interoperability point for observable/reactive libraries.
 * @returns {observable} A minimal observable of state changes.
 * For more information, see the observable proposal:
 * https://github.com/tc39/proposal-observable
 */
调用type:INIT的action来初始化状态

为什么有这一行代码?假设我们没有给currentState一个初始值,那么currentState就是undefined,当我们dispatch一个action的时候,无法在currentState基数上做更新

// 创建store的时候,默认执行init类型的action,让所有的reducer返回它们的初始值
dispatch({ type: ActionTypes.INIT })
compose.js

这个函数主要作用就是将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。理解这个文件最主要需要理解Array.reduce这个api。

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)))
}

比如,调用compose(f, g, h) 就会得到 (…args) => f(g(h(…args)))

combineReducers.js

带注释的combineReducers.js

随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

第一步,浅拷贝reducers 到 finalReducers中
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
  const key = reducerKeys[i]

  if (process.env.NODE_ENV !== 'production') {
    if (typeof reducers[key] === 'undefined') {
      warning(`No reducer provided for key "${key}"`)
    }
  }

  if (typeof reducers[key] === 'function') {
    finalReducers[key] = reducers[key]
  }
}
const finalReducerKeys = Object.keys(finalReducers)
第二步: 检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }

    const type =
      '@@redux/PROBE_UNKNOWN_ACTION_' +
      Math.random()
        .toString(36)
        .substring(7)
        .split('')
        .join('.')
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle ${
            ActionTypes.INIT
          } or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

export default function combineReducers(reducers) {
    //省略第一步的代码
    ......
    let shapeAssertionError
    try {
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
}
第三步:返回一个函数,用于代理所有的reducer
return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      // 对传入的state用getUnexpectedStateShapeWarningMessage做了一个异常检测
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }getUnexpectedStateShapeWarningMessage
    }

    // 表示state是否发生变化
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      // 只要for循环中 有一个 nextStateForKey !== previousStateForKey 就改变hasChanged标识
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 下一次的state只要Object.keys的长度不一样 也直接下nextState
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }

getUnexpectedStateShapeWarningMessage用来打印异常的key信息

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
){...}
applyMiddleware.js

带注释的applyMiddleware.js

之前我们在createStore中有接触到enhancer。回忆一下,当我们的enhancer不为空时,返回的是这样的

return enhancer(createStore)(reducer, preloadedState)

根据这个调用过程我们可以推导出enhancer的函数体的架子应该是这样子的:

function enhancer(createStore) {
  return (reducer,preloadedState) => {
      //逻辑代码
      .......
  }
}

而这里的applyMiddleware是这样的:

function applyMiddlewares(..middlewares){
  return createStore => (...args) => {
    //逻辑代码
    .......
  }
}

我们发现,applyMiddleware的返回就是一个enhancer,下面我们再看其具体的实现逻辑。

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 通过 createStore 方法创建出一个store
    const store = createStore(...args)
    // 定义一个 dispatch,如果在中间件构造过程中调用,抛出错误提示
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    // 每个中间件都需要有 dispatch 和 getState 参数 将其作为中间件调用的store的桥接 ???
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }

    // 将 dispatch 和 getState作为参数 传入到每个中间件中
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 重写 dispatch方法(相当于在原来dispatch的基础上加上各个中间件的处理,再得到结果)
    // 源码的注释中 提到 `redux-thunk`是一个Redux的中间件,而且中间件的处理过程很可能是异步的,所以最好将redux-thunk
    // 作为第一个增强器
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

由于applyMiddleware比较绕,我们借助一个例子+调试来帮助理解。以redux-thunkredux-logger为例

import { createStore, combineReducers, applyMiddleware } from './redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger'


const loggerMiddleware = createLogger()

console.log('thunkMiddleware', thunkMiddleware)

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
  case 'ADD_TODO':
    return state.concat([action.text])
  default:
    return state
  }
}

const reducers = combineReducers({
  todos,
  counter
})


createStore(reducers, applyMiddleware(
  thunkMiddleware,
  loggerMiddleware,
));

然后进入源码调试,在调试之前,我们先找到调用applyMiddleware的地方,我们之前提到过,在createStore.js文件中调用,并且链式调用分别传入了createStore和reducer,preloadState。

 // 如果有 增强器 就在增强器中调用createStore
return enhancer(createStore)(reducer, preloadedState)

  1. 我们可以看到createStore就是createStore.js传给我们的创建store的方法,args就是reducer,preloadState由于创建的时候没传所以为undefined。

  2. 接下来给dispatch设置一个默认值,在接下来chain或者dispatch的构建过程中出现任何错误,最终用户在调dispatch方法时就会提示new Error()中的内容。

  3. 由于在任何一个中间件中,都需要重新构架一个action,所以需要将getState和dispatch传到中间件中。

  4. 构建chain,等到中间件的dispatch集合放入chain变量中(相当于中间件要做的额外的事情)。我们来看下chain,现在表示的是thunk-redux和logger-redux。

  5. 利用compose方法将所有的dispatch串联起来,之前也有提到

比如,调用compose(f, g, h) 就会得到 (...args) => f(g(h(...args)))

那么,这里dispatch就会是 thunkReduxDispatch(loggerReduxDispatch(dipatch(...args)))

最后贴一下redux-thunk的源码

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

bindActionCreators.js

bindActionCreators针对于三种情况有三种返回值,下面我们根据每种情况的返回值去分析。(为了方便理解,我们选择在无集成中间件的情况)

typeof actionCreators === ‘function’
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

// 假设一个demo 
const actionFun = bindActionCreator(actionCreators, dispatch)

// 内部执行
const func1 = actionCreators;
const dispatch = store.dispatch;
const actionFun = function() {
  return dispatch(fun1.apply(this, arguments))
}
typeof actionCreators !== ‘object’ || actionCreators === null
throw new Error(
  `bindActionCreators expected an object or a function, instead received ${
    actionCreators === null ? 'null' : typeof actionCreators
  }. ` +
    `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)

提示开发者actionCreators类型错误,应该是一个非空对象或者是函数

正常情况 actionCreators为Object类型的情况下
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
  const key = keys[i]
  const actionCreator = actionCreators[key]
  if (typeof actionCreator === 'function') {
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  }
}
return boundActionCreators

实际上和第一种情况一样(具体的使用方法可以看下react-redux)

参考链接:

  1. https://segmentfault.com/a/1190000016460366
  2. https://kaiwu.lagou.com/course/courseInfo.htm?courseId=510#/detail/pc?id=4867
  3. https://www.redux.org.cn/docs/api/bindActionCreators.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值