redux源码分析

82 阅读5分钟

createStore

export function createStore(reducer, preloadedState, enhancer) {
  /**
   * 接收三个参数
   * reducer
   * preloadedState 初始化state 可不传
   * enhancer 可以理解为增强器 返回加工后的store
   * preloadedState enhancer都传入函数 或者传入第四个参数也是函数时会抛出异常
   * 如果想使用多个函数加工 可以使用compose方法
   */
  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. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
  }
​
  /**
   * 只传入两个参数 并且第二个参数是函数时
   * 会将preloadedState赋值给enhancer
   * 并初始化preloadedState为 undefined
   */
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
​
  /**
   * enhancer为函数时 会将store api加工后返回
   * 关于此函数详解见https://juejin.cn/post/7174834093933199421
   */
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }
​
    return enhancer(createStore)(reducer, preloadedState)
  }
​
  /**
   * 校验传入的reducer是否函数
   * 不是函数的情况会借助kindOf方法抛出异常
   */
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }
​
  // 记录传入的reducer
  let currentReducer = reducer
  // 记录state
  let currentState = preloadedState
  // 存储listener
  let currentListeners = []
  let nextListeners = currentListeners
  // 当前是否处于dispatch阶段
  let isDispatching = false
  
  // 此函数作用见https://juejin.cn/post/7174430984891285534
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
​
  /**
   * 读取存储管理的状态树
   */
  function getState() {
    /**
     * isDispatching标识当前是否存在dispatch动作
     * 在dispatch的同时不允许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
  }
​
  /**
   * 添加listener监听器 在每次dispatch动作完成后触发listener
   * 举例:可以在每次完成dispatch后在listener中getState观察state变化
   *
   *
   * 你可以在变化监听器里面进行 dispatch(),但你需要注意下面的事项:
   *
   * 1、订阅器(subscriptions)在每次 dispatch() 调用之前都会保存一份快照。
   * 当你在正在调用监听器(listener)的时候订阅(subscribe)或者去掉订阅(unsubscribe),
   * 对当前的 dispatch() 不会有任何影响。但是对于下一次的 dispatch(),无论嵌套与否,都会使用订阅列表里最近的一次快照。
   *
   * 2. 订阅器不应该注意到所有 state 的变化,在订阅器被调用之前,
   * 往往由于嵌套的 dispatch() 导致 state 发生多次的改变。
   * 保证所有的监听器都注册在 dispatch() 启动之前,这样,
   * 在调用监听器的时候就会传入监听器所存在时间里最新的一次 state。
   *
   */
  function subscribe(listener) {
    /**
     * listener必须传入一个函数
     */
    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.push(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
​
      /**
       * 解绑时销毁订阅时的listener
       */
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
​
  /**
   * dispatches an action 这是改变state的唯一方法
   *
   * 将当前state和action传入reducer函数并调用
   * 它的返回值会被作为下一个state 同时listener被触发
   */
  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.'
      )
    }
​
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
​
    try {
      /**
       * isDispatching标识当前处于dispatch中
       * currentReducer处理state并将返回值赋值currentState
       */
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      /**
       * 标识当前dispatch结束
       */
      isDispatching = false
    }
​
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
​
    return action
  }
​
  /**
   * 几乎不使用
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }
​
    currentReducer = nextReducer
​
    dispatch({ type: ActionTypes.REPLACE })
  }
​
  /**
   * 几乎不使用
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        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())
          }
        }
​
        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
​
      [$$observable]() {
        return this
      },
    }
  }
​
  /**
   * 当一个store被创建 redux会自动dispatch an action 使得每个reduce返回初始state
   * type: ActionTypes.INIT 为一个随机值 确保不会命中我们reducer中的条件 进而返回初始state
   */
  dispatch({ type: ActionTypes.INIT })
​
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
}
​
export const legacy_createStore = createStore

combineReducers

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'
​
  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }
​
  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "${kindOf(
        inputState
      )}". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }
​
  /**
   * 通过对比state、reducers的keys
   * 存在state但是不存在reducers中的keys为没有意义的keys值
   *
   * unexpectedKeyCache存在的意义是当发现没有意义的keys时
   * 无论dispatch多少次 都只会warning一次
   */
  const unexpectedKeys = Object.keys(inputState).filter(
    (key) => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )
​
  unexpectedKeys.forEach((key) => {
    unexpectedKeyCache[key] = true
  })
​
  if (action && action.type === ActionTypes.REPLACE) return
​
  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}
​
/**
 * 校验返回的state不能为undefined
 * 即使传入state=undefined
 */
function assertReducerShape(reducers) {
  Object.keys(reducers).forEach((key) => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })
​
    if (typeof initialState === 'undefined') {
      throw new Error(
        `The slice reducer for key "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }
​
    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION(),
      }) === 'undefined'
    ) {
      throw new Error(
        `The slice reducer for key "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle '${ActionTypes.INIT}' or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}
​
/**
 * @param {Object} reducers
 * 入参为一个对象 其值value对应不同的reducer函数 后续这些reducer函数将会合成一个函数
 * 每个reducer都需满足以下规则:
 * 未匹配到的action 传入的state必须原封不动返回
 * 不可以返回undefined combineReducers会抛出异常
 *
 * @returns {Function}
 * 一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象
 */
export default function combineReducers(reducers) {
  /**
   * 相当于进行reducers深拷贝 生成finalReducers对象
   */
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
​
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
​
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
​
  /**
   * 闭包
   * 使用unexpectedKeyCache记录reducer中不存在的keys
   * 当出现有问题的keys时 借助闭包不会警告多次
   * 具体见getUnexpectedStateShapeWarningMessage函数
   */
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }
​
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
​
  return function combination(state = {}, action) {
    debugger
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
​
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }
​
    let hasChanged = false
    const nextState = {}
    /**
     * 逐个遍历并执行reducer
     * 对比dispatch前后state的变化
     * 并通过hasChanged标识判断前后state是否有变化
     */
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const actionType = action && action.type
        throw new Error(
          `When called with an action of type ${
            actionType ? `"${String(actionType)}"` : '(unknown type)'
          }, the slice reducer for key "${key}" returned undefined. ` +
            `To ignore an action, you must explicitly return the previous state. ` +
            `If you want this reducer to hold no value, you can return null instead of undefined.`
        )
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}
​

applyMiddleware

/**
 * 创建一个应用了所有中间件的Enhancer
 *
 * @param middlewares 多个中间件
 * @returns 一个应用了所有中间件的Enhancer
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) =>
    (...args) => {
      const store = createStore(...args)
      let dispatch = () => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed. ' +
            'Other middleware would not be applied to this dispatch.'
        )
      }
​
      // 传入中间件中的store APIs
      const middlewareAPI = {
        getState: store.getState,
        dispatch: (...args) => dispatch(...args),
      }
​
      // 给每一个middleware传入StroeAPI,并且返回一个chain数组
      const chain = middlewares.map((middleware) => middleware(middlewareAPI))
      // 重写dispatch,应用中间件
      dispatch = compose(...chain)(store.dispatch)
​
      return {
        ...store,
        dispatch,
      }
    }
}
​

utils

actionTypes.js

/**
 * 这些是 Redux 保留的私有操作类型。
 * 对于任何未知的操作,您必须返回当前状态。
 * 如果当前状态未定义,则必须返回初始状态。
 * 不要在您的代码中直接引用这些操作类型。
 */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

isPlainObject.js

/**
 * 通过Object.getPrototypeOf方法校验输入参数是否是一个普通对象
 * Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。
 * 此处涉及原型链相关知识:
 * 每个变量都有自己的原型对象,而原型又有其自己的原型,逐级向上构成一条原型链
 * 简单概述下:
 * 数组的原型链:Array => Object => Object => null
 * 对象的原型链:Object => Object => null
 * 在js中顶级对象即为Object 而Object.prototype.__proto__ === null
 * 所以这里利用while (Object.getPrototypeOf(proto) !== null)即可以准确判断一个变量是否为对象
 * 举例说明:
 * 传入一个数组变量
 * Object.getPrototypeOf(obj)为Array
 * 跳出循环的proto为Object
 * 两者不等 即判断传入的数组不是对象
 * 另外注意 不能简单使用typeof判断变量是否为对象 原因如下:
 * typeof [] === 'object'
 * typeof null === 'object'
 *
 * 除此之外 还可以使用 Object.prototype.toString.call() 方法判断
 */
export default function isPlainObject(obj) {
  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
}

index

/**
 * 定义一个‘假函数’用来检查当前代码是否开启了代码压缩
 * 代码压缩过后isCrushed.name !== 'isCrushed'条件成立
 * 如果开启了代码压缩 则抛出警告 开发环境使用压缩文件 会额外增加redux的构建时间
 */
function isCrushed() {}
​
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}
​
export {
  createStore,
  legacy_createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes,
}
​