React系列-扩展状态管理功能及Redux源码解析八

50次阅读

共计 22460 个字符,预计需要花费 57 分钟才能阅读完成。

React 系列

React 系列 — 简单模拟语法(一)
React 系列 — Jsx, 合成事件与 Refs(二)
React 系列 — virtualdom diff 算法实现分析(三)
React 系列 — 从 Mixin 到 HOC 再到 HOOKS(四)
React 系列 — createElement, ReactElement 与 Component 部分源码解析(五)
React 系列 — 从使用 React 了解 Css 的各种使用方案(六)
React 系列 — 从零构建状态管理及 Redux 源码解析(七)
React 系列 — 扩展状态管理功能及 Redux 源码解析(八)

createStore.ts 源码解析

基本功能之后, 我们再回头看看 createStore.ts 里有什么关键代码实现功能的

import $$observable from 'symbol-observable'

import {
  Store,
  PreloadedState,
  StoreEnhancer,
  Dispatch,
  Observer,
  ExtendState
} from './types/store'
import {Action} from './types/actions'
import {Reducer} from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

头部引入了 symbol-observable 做响应式数据, 其余都是一些类型声明和工具函数

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param preloadedState The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @param enhancer The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 *
 * @returns A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */

函数注释来看有三个入参

参数 描述
reducer 给与当前 state 和 action 返回新的 state
preloadedState 初始化 state, 可以从服务器获取或者恢复以前用户序列化的缓存数据
enhancer 可以指定例如中间件的第三方功能增强

返回的 store 可以让你读取 state, 触发 actions, 监听变化

------------- 省略部分代码 ----------------
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.'
  )
}

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
  preloadedState = undefined
}

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

  return enhancer(createStore)(reducer, preloadedState as PreloadedState<
    S
  >) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}

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

都是一些基本的判断和报错机制, 也是我们手写代码省略掉的一步, 中间有关于 enhancer 部分的代码可以后面再讲

let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false

/**
 * This makes a shallow copy of currentListeners so we can use
 * nextListeners as a temporary list while dispatching.
 *
 * This prevents any bugs around consumers calling
 * subscribe/unsubscribe in the middle of a dispatch.
 */
function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()
  }
}

/**
 * Reads the state tree managed by the store.
 *
 * @returns The current state tree of your application.
 */
function getState(): S {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.'
    )
  }

  return currentState as S
}

开头是基本的声明变量, 相比较我们多了一个 nextListenersisDispatching

前者是作为 currentListeners 浅拷贝的临时变量给分发阶段使用的, 这样可以避免在这过程中会用 subscribe/unsubscribe 所导致的 bug

后者是用来锁定状态, 在 dispatching 的过程中做对应逻辑

/**
 * Adds a change listener. It will be called any time an action is dispatched,
 * and some part of the state tree may potentially have changed. You may then
 * call `getState()` to read the current state tree inside the callback.
 *
 * You may call `dispatch()` from a change listener, with the following
 * caveats:
 *
 * 1. The subscriptions are snapshotted just before every `dispatch()` call.
 * If you subscribe or unsubscribe while the listeners are being invoked, this
 * will not have any effect on the `dispatch()` that is currently in progress.
 * However, the next `dispatch()` call, whether nested or not, will use a more
 * recent snapshot of the subscription list.
 *
 * 2. The listener should not expect to see all state changes, as the state
 * might have been updated multiple times during a nested `dispatch()` before
 * the listener is called. It is, however, guaranteed that all subscribers
 * registered before the `dispatch()` started will be called with the latest
 * state by the time it exits.
 *
 * @param listener A callback to be invoked on every dispatch.
 * @returns A function to remove this change listener.
 */
function subscribe(listener: () => void) {if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')
  }

  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() {if (!isSubscribed) {return}

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

比起我们 redux 还做了几层机制

  1. 参数限制
  2. isDispatching状态控制
  3. 每次添加新的监听事件前都会更新最新队列去添加
  4. isSubscribed控制移除事件状态
  5. 通过索引值移除对应事件
/**
 * Dispatches an action. It is the only way to trigger a state change.
 *
 * The `reducer` function, used to create the store, will be called with the
 * current state tree and the given `action`. Its return value will
 * be considered the **next** state of the tree, and the change listeners
 * will be notified.
 *
 * The base implementation only supports plain object actions. If you want to
 * dispatch a Promise, an Observable, a thunk, or something else, you need to
 * wrap your store creating function into the corresponding middleware. For
 * example, see the documentation for the `redux-thunk` package. Even the
 * middleware will eventually dispatch plain object actions using this method.
 *
 * @param action A plain object representing“what changed”. It is
 * a good idea to keep actions serializable so you can record and replay user
 * sessions, or use the time travelling `redux-devtools`. An action must have
 * a `type` property which may not be `undefined`. It is a good idea to use
 * string constants for action types.
 *
 * @returns For convenience, the same action object you dispatched.
 *
 * Note that, if you use a custom middleware, it may wrap `dispatch()` to
 * return something else (for example, a Promise you can await).
 */
function dispatch(action: A) {if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects.' +
        'Use custom middleware for async actions.'
    )
  }

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

  if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {isDispatching = false}

  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]
    listener()}

  return action
}

更加多的逻辑代码

  1. 参数判断
  2. isDispatching状态控制
  3. 纯函数更新数据代码加了捕获机制
  4. 每次都拿最新的监听队列遍历触发
  5. 返回原样 action
/**
 * Replaces the reducer currently used by the store to calculate the state.
 *
 * You might need this if your app implements code splitting and you want to
 * load some of the reducers dynamically. You might also need this if you
 * implement a hot reloading mechanism for Redux.
 *
 * @param nextReducer The reducer for the store to use instead.
 * @returns The same store instance with a new reducer in place.
 */
function replaceReducer<NewState, NewActions extends A>(nextReducer: Reducer<NewState, NewActions>): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {if (typeof nextReducer !== 'function') {throw new Error('Expected the nextReducer to be a function.')
  }

  // TODO: do this more elegantly
  ;((currentReducer as unknown) as Reducer<
    NewState,
    NewActions
  >) = nextReducer

  // This action has a similiar effect to ActionTypes.INIT.
  // Any reducers that existed in both the new and old rootReducer
  // will receive the previous state. This effectively populates
  // the new state tree with any relevant data from the old one.
  dispatch({type: ActionTypes.REPLACE} as A)
  // change the type of the store by casting it to the new store
  return (store as unknown) as Store<
    ExtendState<NewState, StateExt>,
    NewActions,
    StateExt,
    Ext
  > &
    Ext
}

reducers 的替代方法, 一般场景比较少用到, 基本代码不多

/**
 * Interoperability point for observable/reactive libraries.
 * @returns A minimal observable of state changes.
 * For more information, see the observable proposal:
 * https://github.com/tc39/proposal-observable
 */
function observable() {
  const outerSubscribe = subscribe
  return {
    /**
     * The minimal observable subscription method.
     * @param observer Any object that can be used as an observer.
     * The observer object should have a `next` method.
     * @returns An object with an `unsubscribe` method that can
     * be used to unsubscribe the observable from the store, and prevent further
     * emission of values from the observable.
     */
    subscribe(observer: unknown) {if (typeof observer !== 'object' || observer === null) {throw new TypeError('Expected the observer to be an object.')
      }

      function observeState() {
        const observerAsObserver = observer as Observer<S>
        if (observerAsObserver.next) {observerAsObserver.next(getState())
        }
      }

      observeState()
      const unsubscribe = outerSubscribe(observeState)
      return {unsubscribe}
    },

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

观察者模式的实现库做监听事件

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({type: ActionTypes.INIT} as A)

const store = ({
  dispatch: dispatch as Dispatch<A>,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store

方法的最后会触发一个 ActionTypes.INIT 的 action 做初始化数据, 返回一个包含暴露的方法对象出去.

createStore.ts 源码地址

我们再看看 actionTypes.ts 源码做了什么

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */

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

不在于得到什么结果, 只需要它是复杂难以跟开发定义的 action 重复就行了, 为了执行一次 distapch 获取到初始的 state.

actionTypes.ts 源码地址

实例六(优化)

学习完 createStore.ts 源码之后我们可以将一些好的地方引入我们的库里

  1. nextListeners充当临时变量传递给其他函数使用
  2. isDispatching作为状态标记判断流程
  3. dispatch函数增加容错机制, 返回原样action
  4. isSubscribed控制监听事件解绑机制, 从重新过滤赋值改成根据索引值移除事件
  5. 增加一个不易重复的 action 执行预触发返回每个 reducer 的初始数据

createStore.js

function createStore (initStore = {}, reducer) {
  // 唯一数据源
  let state = initStore
  // 监听队列
  let listenList = []
  // 监听队列浅拷贝
  let nextListeners = listenList
  // 是否 dispatch 中
  let isDispatching = false

  // 浅拷贝
  function ensureCanMutateNextListeners () {if (nextListeners === listenList) {nextListeners = listenList.slice()
    }
  }

  // 唯一获取数据函数
  function getState () {
    // 输出警告
    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.'
      )
    }
    return state
  }

  // 纯函数来执行修改, 只返回最新数据
  const dispatch = (action) => {
    // 严格控制 dispatch, 不得中途再次发送
    if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
    }

    // 增加意外防止操作
    try {
      isDispatching = true
      state = reducer(state, action)
    } finally {isDispatching = false}

    // 获取更改后的数据同时获取最新队列
    const listeners = (listenList = nextListeners)
    // 替换成原始遍历提高性能, 遍历触发事件
    for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]
      listener()}

    // 为了方便将 action 原样返回
    return action
  }

  // 添加监听器, 同时返回解绑该事件的函数
  const subscribe = (fn) => {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.')
    }

    // 占位标记
    let isSubscribed = true
    // 每次添加监听事件时浅拷贝最新队列
    ensureCanMutateNextListeners()
    nextListeners.push(fn)

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

      if (isDispatching) {
        throw new Error('You may not unsubscribe from a store listener while the reducer is executing.')
      }
      isSubscribed = false
      // 每次移除监听事件时浅拷贝最新队列
      ensureCanMutateNextListeners()
      // 根据索引值删除比 filter 过滤重新赋值效率高
      const index = nextListeners.indexOf(fn)
      nextListeners.splice(index, 1)
      listenList = null
    }
  }

  // 默认触发一次 dispatch 以获取各个 reduce 的初始数据
  dispatch({type: `@@redux/INIT${Math.random()
      .toString(36)
      .substring(7)
      .split('')
      .join('.')}`
  })

  return {
    getState,
    dispatch,
    subscribe
  }
}

文章的完整代码可以直接查看 demo6

applyMiddleware 源码解析

createStore函数还有一个入参 enhancer 我们之前没实现,

React 提供使用中间件的唯一方式是 applyMiddleware 函数, 我们看一下怎么介绍它的

Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时,middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息

demo

import {createStore, applyMiddleware} from 'redux'
import todos from './reducers'

function logger({getState}) {return (next) => (action) => {console.log('will dispatch', action)

    // 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 一般会是 action 本身,除非
    // 后面的 middleware 修改了它。return returnValue
  }
}

let store = createStore(
  todos,
  ['Use Redux'],
  applyMiddleware(logger)
)

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (将打印如下信息:)
// will dispatch: {type: 'ADD_TODO', text: 'Understand the middleware'}
// state after dispatch: ['Use Redux', 'Understand the middleware']

logger是通用的中间件格式, 这是一个三层嵌套函数, 分别是 {getState, dispatch}, next(其实是下一个包装后的中间件) 和 action 入参, 其实相当于

function middleware ({getState, dispatch}) {return (next) => {return (action) => {
      // dosomething
      // 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
      // dosomething
      // 一般会是 action 本身,除非
      // 后面的 middleware 修改了它。return returnValue
    }
  }
}

知道这个基本规则之后我们就可以看看 applyMiddleware 里面做了什么

我们看一下先过一下源码里面做了些什么

import compose from './compose'
import {Middleware, MiddlewareAPI} from './types/middleware'
import {AnyAction} from './types/actions'
import {StoreEnhancer, StoreCreator, Dispatch} from './types/store'
import {Reducer} from './types/reducers'

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param middlewares The middleware chain to be applied.
 * @returns A store enhancer applying the middleware.
 *
 * @template Ext Dispatch signature added by a middleware.
 * @template S The type of the state supported by a middleware.
 */

总的来说就是创建一个应用程序的中间件去增强 redux store 的 dispatch 方法, 对多种类的任务来说非常便利, 例如以简洁的方式表达异步流程或者输出每个 action payload 的日志, 而每个中间件都会拿到 dispatchgetState入参

------------- 省略部分代码 ----------------
export default function applyMiddleware(...middlewares: Middleware[]
): StoreEnhancer<any> {return (createStore: StoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    ...args: any[]) => {const store = createStore(reducer, ...args)
    let dispatch: Dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed.' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

大致分析一下代码里做了什么操作

  1. 接收多个中间件入参
  2. 接收 createStore 函数
  3. 接收 reducer 和其他入参
  4. 用上面的参数实例化新的store
  5. 定义dispatch, 抛出异常 ’ 不允许在构建中间件的时候 dispatch, 因为其他中间件不会被应用到该次 dispatch’
  6. 构建 middlewareAPI 对象, 暴露出对应的方法, 目的是让每个执行中间件都是一样的入参条件
  7. 遍历中间件返回执行 middlewareAPI 之后的新函数数组
  8. 重新赋值 dispatch 函数为 compose 之后的返回值
  9. 最终抛出 store 实例的属性方法和包装后的新 dispatch 方法

applyMiddleware.ts 源码地址

上面有一个没解析的 compose 函数, 源码如下

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for the
 * resulting composite function.
 *
 * @param funcs The functions to compose.
 * @returns A function obtained by composing the argument functions from right
 *   to left. For example, `compose(f, g, h)` is identical to doing
 *   `(...args) => f(g(h(...args)))`.
 */
------------- 省略部分代码 ----------------
export default function compose(...funcs: Function[]) {if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

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

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

总的来说, 除了类型判断, 实际代码只有一个 reduce 的应用 …, 这里可以知道每个中间件是有顺序关系的, 所以应用的时候需要注意一下.

compose.ts 源码地址

applyMiddleware的相关源码已经过了一遍, 剩下我们回顾一下在 createStore 里是怎么处理相关逻辑的, 放心, 真的不多

------------- 省略部分代码 ----------------
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState as PreloadedState<
    S
  >) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
------------- 省略部分代码 ----------------

检查到传入 enhancer 的时候直接中断流程, 返回执行结果,

我们再重新梳理一下流程:

调用方式

createStore(reducer, preloadedState, applyMiddleware(f1, f2, ...fn))

在 createStore 里如果检测到 enhancer 入参会

return enhancer(createStore)(reducer, preloadedState)
相当于
return applyMiddleware(f1, f2, ...fn)(createStore)(reducer, preloadedState)

在 applyMiddleware 源码可得知

// 初始化一个 store
let store = createStore(reducer, preloadedState);

// 每个中间件拿到的入参
const middlewareAPI: MiddlewareAPI = {
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args)
}

// 遍历的中间件大概流程这样子
middlewares = [f1(middlewareAPI) => s1(next) => t1(...arg)
    f2(middlewareAPI) => s2(next) => t2(...arg)
    fn(middlewareAPI) => sn(next) => tn(...arg)
]

// chain 得到的数组就长这样子
const chain = [s1(next) => t1(...arg)
    s2(next) => t2(...arg)
    sn(next) => tn(...arg)
]

// compose 经过 reduce 方法包装返回
const composeFn = s1((s2(sn(next) => tn(...arg))()) => t2(...arg))()) => t1(...arg)

// 最终返回的 dispatch 方法
dispatch = (composeFn)(store.dispatch)

// 整体流程
const applyMiddleware = (中间件数组) => (createStore) => (reducer, preloadedState) => {...store, dispatch: compose(...chain)(store.dispatch)}

这时候再回到中间件的通用代码可以知道

function middleware ({getState, dispatch}) {return (next) => {return (action) => {
      // dosomething
      // 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
      // dosomething
      // 一般会是 action 本身,除非
      // 后面的 middleware 修改了它。return returnValue
    }
  }
}

阶段一: 接收相同的初始{getState, dispatch}

阶段二: 接收经过下一个中间件包装后的 dispatch 调用

阶段三: 接收 action 处理某些逻辑之后原样返回, 一般不该修改 action

效果: dispatch 一个 action, 会用倒序的方式逐一经过每个中间件的流程形成链式调用, 并且前后一般不需要关心做些什么操作.

applyMiddleware 简单实现

我们既然已经知道了它的实现思路, 接下来就可以简单封装一个了

compose.js

function compose (...funcs) {if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return arg
  }

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

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

applyMiddleware.js

// 接收中间件数组
function applyMiddleware (...middlewares) {
  // 接收 createStore 函数和 reducer 和其他参数
  return (createStore) => (reducer, ...args) => {
    // 这就是原始的实例化 store, 所以 applyMiddleware 方法其实就是围绕在原始 store 的基础上添加功能
    const store = createStore(reducer, ...args)

    // 先初始化 dispatch 方法占位, 但是此时执行会抛出异常
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed.' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    /**
     * 构建中间件第一层运行的入参对象, 保证每个中间件都是一样的参数条件, 所以上面的抛出异常也是如此
     * applyMiddleware([f1(middlewareAPI) => s1(next) => t1(...arg)
        fn(middlewareAPI) => sn(next) => tn(...arg)
     * ])
     *
     */
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }

    // 遍历运行每个中间件返回新的数组
    // chain = [s1(next) => t1(...arg), ...sn(next) => tn(...arg)]
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))

    /* 返回增强功能后的 dispatch 方法
    dispatch = (s1(sn(next) => tn(...arg))()) => t1(...arg))(store.dispatch) */
    dispatch = compose(...chain)(store.dispatch)

    // 替代原始的 store 对象
    return {
      ...store,
      dispatch
    }
  }
}

因为新增了增强功能, 所以我们也要把 createStore 修改一下, 按照源码对应一下

因为参数里只有 reducer 是必选, 其他两者都是可选, 所以我们还要把入参顺序也替换一下

createStore.js

function createStore (reducer, initStore = {}, enhancer) {
  // 处理一下参数问题
  if (typeof initStore === 'function' && typeof enhancer === 'undefined') {
    enhancer = initStore
    initStore = undefined
  }

  // 劫持 enhancer
  if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')
    }
    // 返回包装后的 store
    return enhancer(createStore)(reducer, initStore)
  }
  ------------- 省略部分代码 ----------------
  return {
    getState,
    dispatch,
    subscribe
  }
}

最后只剩执行函数

store.js

// 初始数据
const initStore = {
  arNum: 0,
  mdNum: 1
}

// 日志中间件
function logger ({getState}) {return (next) => (action) => {console.log('will dispatch', action)

    // 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 一般会是 action 本身,除非
    // 后面的 middleware 修改了它。return returnValue
  }
}

// 实例化 store
let store = createStore(reducers, initStore, applyMiddleware(logger))

现在在 index.html 引入新的依赖执行可以发现也能正常输出日志了.

文章的完整代码可以直接查看 demo7

bindActionCreators 源码解析

其实上面就已经算是完成了一个简单的状态管理器了, 但是我们从 Redux 的 API 里其实能够看到还有一个方法是我们还没了解过的, 就大概说说

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

至于什么情景会遇到需要使用

你或许要问:为什么不直接把 action creator 绑定到 store 实例上,就像传统的 Flux 那样?问题在于,这对于需要在服务端进行渲染的同构应用会有问题。多数情况下,你的每个请求都需要一个独立的 store 实例,这样你可以为它们提供不同的数据,但是在定义的时候绑定 action creator,你就只能使用一个唯一的 store 实例来对应所有请求了。

省略掉类型判断后的源码

import {Dispatch} from './types/store'
import {
  AnyAction,
  ActionCreator,
  ActionCreatorsMapObject
} from './types/actions'

function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {return function(this: any, ...args: any[]) {return dispatch(actionCreator.apply(this, args))
  }
}

/**
 * Turns an object whose values are action creators, into an object with the
 * same keys, but with every function wrapped into a `dispatch` call so they
 * may be invoked directly. This is just a convenience method, as you can call
 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 *
 * For convenience, you can also pass an action creator as the first argument,
 * and get a dispatch wrapped function in return.
 *
 * @param actionCreators An object whose values are action
 * creator functions. One handy way to obtain it is to use ES6 `import * as`
 * syntax. You may also pass a single function.
 *
 * @param dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call. If you passed a
 * function as `actionCreators`, the return value will also be a single
 * function.
 */
------------- 省略部分代码 ----------------
export default function bindActionCreators(
  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch)
  }

  if (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"?`
    )
  }

  const boundActionCreators: ActionCreatorsMapObject = {}
  for (const key in actionCreators) {const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreator返回绑定 this 指向的新函数

bindActionCreators做了三件事:

  1. 如果 actionCreators 是函数, 直接返回调用bindActionCreator
  2. 如果 actionCreators 非对象非 null 抛出异常
  3. 如果 actionCreators 是可迭代对象, 返回遍历调用 bindActionCreator 包装后的对象

因为我们的 demo 不需要用到, 就没必要实现了, 大家知道原理即可

bindActionCreators.ts 源码地址

正文完
 0