Redux 源码讲解 — 1.createStore

669 阅读5分钟

回忆一下我们使用 redux 的流程,首先,我们从 Redux 中引入 createStore 方法,然后调用 createStore 方法,并将 Reducer 作为参数传入,用来生成 Store 。为了接收到对应的 State 更新,我们先执行 Storesubscribe 方法,将render 作为监听函数传入。然后我们就可以 dispatch action 了,对应更新 viewState

react-redux-overview.png

可以看出应用的入口就是这个 createStore 函数,createStore 主要用于 Store 的生成,我们就从它开始阅读,已进入函数就是又臭又长的重载函数,函数前半段就是对参数的判断,我们就挑参数最全的入口来看并且忽略掉参数判断部分:

export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
  // ...
}

抛开参数处理,我们可以看到声明了一系列函数,然后执行了dispatch方法,最后暴露了dispatchsubscribe……几个方法。这里dispatch了一个init Action是为了生成初始的State树。

export default function createStore(reducer, preloadedState, enhancer) {
	let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false
  
	function getState(): S { /** some code */ }
  function subscribe(listener: () => void) { /** some code */ }
  function dispatch(action: A) { /** some code */ }
  function replaceReducer<NewState, NewActions extends A>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext { /** some code */ }
  function observable() { /** some code */ }
  
  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.INITREPLACEredux 内置的 action,在初始化和更换 reducer 的时候触发,执行的效果就是用传入的 preloadState 初始化一下状态树,然后执行一下订阅函数。

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

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

我们先挑两个简单的函数看下,getStatereplaceReducer ,其中 getState 只是返回了当前的状态。replaceReducer 是替换了当前的 Reducer 并重新初始化了 State 树。这两个方法比较简单:

replaceReducer

replaceReducer 更改了 currentReducer 缓存的 reducer,通过 ActionTypes.REPLACE 来初始化最后返回 store:

export default function createStore(reducer, preloadedState, enhancer) {
	let currentReducer = reducer
  // ...
	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.')
    }

		// 替换 reducer
    ;((currentReducer as unknown) as Reducer<
      NewState,
      NewActions
    >) = nextReducer

    // ActionTypes.REPLACE 和 INIT 的影响是一致的,都会初始化 reducer tree
    dispatch({ type: ActionTypes.REPLACE } as A)
      
    return (store as unknown) as Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext
  }

getState

getState 就太简单了,currentState 是通过闭包缓存的 state 数据,上面我们说过 redux 只有一个单一的状态树,所以返回的时候也只会返回一个 state:

export default function createStore(reducer, preloadedState, enhancer) {
  let currentState = preloadedState as S
  //...
  
	function getState(): S {
    if (isDispatching) {
      throw new Error(/** long error msg */)
    }

    return currentState as S
  }
}

这里 dispatch 的过程中不能发生 getState 道理也很简单,就相当于是一个读写锁的机制,读数据的时候其他人也可以读而也可以写,写数据的不论是写还是读都不可以发生。

后面的 subscriber 可以看作和 getState 一个性质作为读行为。

dispatch

store.dispatch(action) 是唯一的派发 action 来更改 state 状态的函数,先来看看它参数检测的部分,首先 action 必须要有 type 属性:

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

		// ...
  }

其次就是检测 action 是否是一个 plainObject,即该对象由 Object 构造函数创建,或者 [[Prototype]]null ,这样是为了防止副作用的发生,我们可以看看 isPlainObject 这个检测函数:

通过一个 while 循环拿到参数对象原型链的最后一个值,然后和对象原型链上的第一个值比较

如果对象是一个 PlainObject 那最后一个和第一个都是 Object.prototype,注意这里还少了一个 edge case 的判断,即 objObject.create(null) 构建的时候,按道理说这里应该允许这种

function isPlainObject(obj: any): boolean {
  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
}

接下来我们回到 dispatch 这个函数,首先同一时间只能有一个 dipatch 出发,不能并发的对 store 存储进行更新,会造成更新丢失,之后我们通过 currentReducer(currentState, action) 更新 currentState,最后执行订阅的 listener 更新推送到视图或者其他订阅者:

export default function createStore(reducer, preloadedState, enhancer) {
  let isDispatching = false
  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  
  function dispatch(action: A) {
    // ...
		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
  }
}

subscribe

订阅函数subscribe 的主要作用是注册监听事件,然后返回取消订阅的函数,它把所有的订阅函数统一放一个数组里,只维护这个数组。在 react 中这个订阅函数通常就是 () => ReactDom.render(<App />) 这样一个渲染函数:

下面来解释一下为什么我们有两个监听队列 currentListenersnextListeners,这是为了做到实时性,首先在 dispatch 后半部分我们是可以触发订阅的,比如上面的 dispatch 执行到执行 listener 监听者函数的时候发生了添加订阅的行为,那么 redux 的实现原则如下:

\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.

dispatch 执行的一定是 currentListeners的快照,新添加的订阅函数不会被执行,而下一次 dispatch 一定会执行新添加的订阅函数。所以说我们有两个订阅队列,每次 subscribe 更新的是 nextListeners,而 dispatch 执行函数前获取的是当时最新的队列 const listeners = (currentListeners = nextListeners)

每次准备更新 nextListeners 的时候都产生一个新的切片防止其对 currentListener 的影响:

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

了解实时性的背后原理之后,subscribe 所做的就只是向 nextListeners 推入一个监听函数然后返回一个取消订阅的函数:

export default function createStore(reducer, preloadedState, enhancer) {
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false
  
	function subscribe(listener: () => void) {
    // 参数判断
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
		// 检测 dispatch 状态,如果正在 dispatch 的状态中则不能添加订阅
    if (isDispatching) {
      throw new Error(/** long error msg */)
    }
    ensureCanMutateNextListeners()
    // 两个队列
    nextListeners.push(listener)
		// 开启订阅
    let isSubscribed = true
    return function unsubscribe() {
      if (!isSubscribed) return
      if (isDispatching) throw new Error(/** long error msg */)

      isSubscribed = false
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
}