Redux源码之createStore

396 阅读3分钟

前言

本文会按照 createStore 的源码,由上至下分别从参数、内部定义的变量、getState、subScribe订阅、dispatch派发、replaceReducer 替换 reducer 几个方面进行介绍。关于combineReducers applyMiddleware 的源码在 上一篇 有进行说明。

参数处理

createStore(reducer, preloadedState, enhancer)

createStore 接收三个参数:reducer、初始化的 state、中间件(加强dispatch)

// reducer 必须是 function
if (typeof reducer !== 'function') {
    throw new Error(/*...*/)
}
// 第二、三个参数不能同时为 function
if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
    throw new Error(/*...*/)
}
// 如果只传了两个参数,并且第二个参数是 function,那么默认第二个参数就是 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}
// 如果有第三个参数并且是个 function 就执行这个 function,这里是对中间件的处理
if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
        throw new Error(/*...*/)
    }
    return enhancer(createStore)(
        reducer,
        preloadedState,
    )
}

通过上面的代码可以看到,第二个参数 preloadedState 不是必须传的,那当不传第二个参数,state 是如何初始化的呢?

dispatch({ type: ActionTypes.INIT });

在 createStore 底部执行了一次 type 为随机的派发,这时执行 reducer,返回结果就会赋给 currentState 完成初始化

变量

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
let isDispatching = false

currentReducer 就是我们传入 createStore 的 reducer
currentState 就是初始化的 state
currentListeners 是一个 map用来存放订阅事件
nextListeners 是 currentListeners 的副本
isDispatching 用来判断是否处于 dispatch

getState

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

getState 在不是 dispatch 的状态下,执行返回当前的 state。

subscribe

function subscribe(listener: () => void) {
    // listener 必须是一个 function
    if (typeof listener !== 'function') {
        throw new Error(
            `Expected the listener to be a function. Instead, received: '${kindOf(
                listener
            )}'`
        )
    }
    // 在 dispatch 时不可以订阅
    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
    // 确保下次的监听队列是最新的
    ensureCanMutateNextListeners()
    // 向 nextListeners 加入新的 listener
    const listenerId = listenerIdCounter++
    nextListeners.set(listenerId, listener)
    // 退订
    return function unsubscribe() {
        // 如果没订阅,直接返回
        if (!isSubscribed) {
            return
        }
        // dispatch 中不能退订
        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()
        // 将退订的方法从新的监听队列移除
        nextListeners.delete(listenerId)
        // 清空当前监听队列
        currentListeners = null
    }
}

ensureCanMutateNextListeners

function ensureCanMutateNextListeners() {
    // 判断新监听队列和当前队列是否指向同一地址
    if (nextListeners === currentListeners) {
        // 浅拷贝一份 currentListeners。currentListeners 和 nextListeners 不再指向同一地址 后面会在 nextListeners 里面进行新的监听事件添加。
        nextListeners = new Map()
        currentListeners.forEach((listener, key) => {
            nextListeners.set(key, listener)
        })
    }
}

为什么采用两个监听队列?

currentListeners 代表的是当前的监听队列,nextListeners代表的是下次要执行的监听队列,那么为什么需要两个监听队列呢? 我们设想一个场景,如果我们在订阅时进行嵌套,如下

subscribe(() => {
    console.log("第一次订阅")
    subscribe(() => {
        console.log("嵌套订阅")
    })
});

这里在 subscribe 订阅时嵌套 再次订阅,嵌套订阅并不会立即执行,而是会被保存在 nextListeners 在下次执行 这样可以保证在订阅过程中任何新订阅和取消订阅不会对当前的监听产生影响。

dispatch

function dispatch(action) {
    // 首先判断 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.`
      )
    }
    // 判断 action 是否存在 type 属性
    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.'
      )
    }
    // 判断 action 的 type 是否是一个对象
    if (typeof action.type !== 'string') {
      throw new Error(
        `Action "type" property must be a string. Instead, the actual type was: '${kindOf(
          action.type
        )}'. Value was: '${action.type}' (stringified)`
      )
    }
    // 如果正处于 dispatch 状态,不可以再进行 dispatch 会抛出错误
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      // 到这里就可以开始进行 dispatch 了,首先把状态设置为 正在 dispatch
      isDispatching = true
      // 执行 reducer,传入 旧的state 和 action,将执行结果也就是新的 state 赋值给 currentState
      currentState = currentReducer(currentState, action)
    } finally {
      // 无论是否成功,最后都要关闭 dispatch 状态
      isDispatching = false
    }
    // 将 nextListeners 赋值给 currentListeners,此时他们指向同一地址
    const listeners = (currentListeners = nextListeners)
    // 遍历执行所有的监听函数
    listeners.forEach(listener => {
      listener()
    })
    return action
  }

isPlainObject

export default function isPlainObject(obj): boolean {
  // action 必须是个非空对象
  if (typeof obj !== 'object' || obj === null) return false
  
  let proto = obj
  // 获取到 obj 原型链非 null 的顶层,赋值给 proto
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  // 判断 obj 的原型是否为 proto action。其实就是判断 obj 是否存在继承关系,存在则返回 false
  return Object.getPrototypeOf(obj) === proto
}

replaceReducer

  function replaceReducer(nextReducer) {
    // 新的 reducer 必须是一个 function
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }
    // 进行替换
    currentReducer = nextReducer
    // 进行一次派发
    dispatch({ type: ActionTypes.REPLACE })
  }

replaceReducer 就是简单的进行替换然后进行一次派发