createStore | Sanitarium

createStore

大约 7 分钟

createStore

只包含一个createStore函数,用来创建store;该函数输入reducer函数、初始状态preloadedState以及对 store 功能进行增强的高阶函数enhancer

Redux 提供的数据流管理的功能基本就集成在这个函数中了。

入参处理

if (
  (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
  (typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
  ...
}

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  ...
}

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    ...
  }

  return enhancer(createStore)(reducer, preloadedState)
}

if (typeof reducer !== 'function') {
  ...
}

函数开始部分的这一大堆判断代码都是用来处理参数的,允许用户以以下方式调用createStore

createStore(reducer, preloadedState, enhancer);
createStore(reducer, preloadedState);
createStore(reducer, enhancer);
createStore(reducer);

如果出现了不符合上述情况的入参,则会报错。

变量及函数定义

接下来定义了一些函数内部变量:

// 当前的 reducer 函数
let currentReducer = reducer
// 当前状态
let currentState = preloadedState
// 当前的事件处理函数
let currentListeners = []
// 下个阶段的事件处理函数
let nextListeners = currentListeners
// 指示是否在执行 reducer 函数
let isDispatching = false

然后定义了一些函数来操作上述的变量,实现 store 的功能,并最后将它们作为一个模块导出。

getState

getState用来获取当前的全局状态,逻辑较为简单:

/**
 * Reads the state tree managed by the store.
 *
 * @returns {any} The current state tree of your application.
 */
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 currentState
}

subscribe 和 ensureCanMutateNextListeners

subscribe函数接收一个函数作为listener,将它加入订阅者数组中,并返回一个取消订阅的函数。

在批量调用订阅者的时候,即使又调用subscribe进行了订阅或取消订阅,也不会改变当前执行的任务队列,而是会将改变保存在nextListener中,否则可能造成有些订阅者没有被调用的情况;每次批量调用之前都会进行快照才会取得最新的订阅者数组。

function subscribe(listener) {
  // 订阅者必须是函数
  if (typeof listener !== 'function') {
    throw new Error(
      `Expected the listener to be a function. Instead, received: '${kindOf(
        listener
      )}'`
    )
  }

  // reducer 执行过程中不允许改变订阅者,因为 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/store#subscribelistener for more details.'
    )
  }

  // 用来标识是否已订阅,如果已经取消订阅,就不再执行取消订阅的逻辑了
  let isSubscribed = true

  // 确保 nextListeners 和 currentListeners 不是一个数组
  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/store#subscribelistener for more details.'
      )
    }

    isSubscribed = false

    // 将当前订阅者移除
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
    currentListeners = null
  }
}

值得一提的是,当要改变nextListener之前,要调用一次ensureCanMutateNextListeners函数,确保nextListenerscurrentListeners指向的不是同一个数组,这样才能只改变nextListeners而不改变currentListeners

function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    // 如果两个变量指向同一个数组,则复制一份新的数组作为 nextListeners
    nextListeners = currentListeners.slice()
  }
}

dispatch

dispatch用来分发一个action,并将它传入reducer函数,来对全局状态树进行修改;并执行所有的回调函数。

dispatch的入参为一个action,这个action必须是一个 plainObject,还必须有一个type字段,函数会对入参进行检查,如果传入了不满足条件的aciton,就会抛出错误。

isDispatching在调用reducer的过程中会被设置为true。在调用reducer之前,会判断当前是否正在调用reducer,如果是则抛出一个错误,因为reducer应当是一个纯函数,只用来改变state,如果在reducer中再进行dispatch,当第一个reducer执行完之后,结果会覆盖第二个reducer调用的结果,可能会导致状态改变的丢失。

调用完成之后,将reducer的返回值设置为新的state,然后对当前的nextListeners进行快照,确定此次需要调用的任务队列,并调用所有的订阅者。保存快照是因为在调用listeners的时候,有可能会对listeners这个数组进行修改,而此次通知的应当是执行完reducer那一时刻的所有订阅者,因此需要先快照保存下来进行调用。

最后,将action返回,便于下一个中间件进行调用。

function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      `Actions must be plain objects. Instead, the actual type was: '${kindOf(
        action
      )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
    )
  }

  if (typeof action.type === 'undefined') {
    throw new Error(
      'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
    )
  }

  // 不能在 reducer 中调用 dispatch
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    // 置为 true,表明正在调用 reducer
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // 将 nextListeners 赋值给 currentListeners ,并快照
  const listeners = (currentListeners = nextListeners)
  // 调用所有 listeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
}

replaceReducer

用于更改 reducer 函数。如果某些模块是动态加载的,当动态加载过来了以后,reducer函数可能需要发生变化,这里就提供了一个方法来修改当前的reducer函数。模块热加载的机制下也需要使用它。

然后 dispatch 一个 类型为REPLACE的 action,来执行一次 reducer ,创建新的全局状态树。

function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error(
      `Expected the nextReducer to be a function. Instead, received: '${kindOf(
        nextReducer
      )}`
    )
  }

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

observable

这个函数用于将创建的store配置成可观察对象。

store作为一个可观察对象,应当有一个subscribe方法,可以传入一个观察者对象,加入这个可观察对象的观察者列表中;当可观察对象发生变化时,会调用所有订阅了它的观察者的next方法,并将当前的状态传入进去。

Redux 通过在store对象上部署Symbol.observable这个属性来实现可观察对象。该属性是一个函数,返回一个对象,有subscribe方法和Symbol.observable方法,subscribe方法通过订阅回调函数的方式,实现了在state发生变化时,调用观察者的next方法,使store也成为了一个可观察对象,可以通过订阅观察者的方式来使用。

function observable() {
  const outerSubscribe = subscribe
  return {
    subscribe(observer) {
      // 观察者对象必须是一个 object
      if (typeof observer !== 'object' || observer === null) {
        throw new TypeError(
          `Expected the observer to be an object. Instead, received: '${kindOf(
            observer
          )}'`
        )
      }

      // 当和状态发生改变的时候,
      function observeState() {
        if (observer.next) {
          observer.next(getState())
        }
      }

      // 初次订阅,执行一次观察者的 next
      observeState()
      // 将 observeState 作为订阅者,这样每次状态变化时,都会调用观察者的 next 方法
      const unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },

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

初始化

dispatch 一个 action,触发 reducer 的初始化执行,用来构建状态树的初始化状态。

dispatch({ type: ActionTypes.INIT })

返回 store 对象

最后,返回store对象,它是一个包含了dispatch/subscribe/getState/replaceReducer/$$observable属性的对象,外界通过操作对象的这些方法来对store进行操作。

return {
  dispatch,
  subscribe,
  getState,
  replaceReducer,
  [$$observable]: observable,
}
上次编辑于:
贡献者: mickmetalholic