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源码地址


Afterward
621 声望62 粉丝

努力去做,对的坚持,静待结果