Redux源码浅析

239 阅读9分钟

从Vue转React技术栈快一年了,这几天断断续续翻了下Redux(V4.0.4)的源码,还是有点点收获。

调试

由于我们看源码时候,很多时候需要调试,方便我们对流程的梳理,调试源码还是很方便的。只需要在你的项目中node_module中redux包下的package.json修改下引用路径即可。

比如Redux中默认package.json


 "main": "lib/redux.js",
 "unpkg": "dist/redux.js",
 "module": "es/redux.js",

我们在自身项目模块的类型修改main或unpkg、module为src/index.js即可。

目录结构

整体目录结构是很清晰的,index.ts是入口文件,types目录是对应TS类型注解文件,utils目录是工具方法。

index.ts

作为入口文件,主要做了两件事。

  • 导出类型注解
export {
  CombinedState,
  PreloadedState,
  Dispatch,
  Unsubscribe,
  Observable,
  Observer,
  Store,
  StoreCreator,
  StoreEnhancer,
  StoreEnhancerStoreCreator,
  ExtendState
} from './types/store'
// reducers
export {
  Reducer,
  ReducerFromReducersMapObject,
  ReducersMapObject,
  StateFromReducersMapObject,
  ActionFromReducer,
  ActionFromReducersMapObject
} from './types/reducers'
// action creators
export { ActionCreator, ActionCreatorsMapObject } from './types/actions'
// middleware
export { MiddlewareAPI, Middleware } from './types/middleware'
// actions
export { Action, AnyAction } from './types/actions'

  • 导出暴露的API

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

createStore.ts

首先看createStore方法,再看之前我们先熟悉一些概念。

  • createStore方法会创建一个store、存储状态树。
  • 修改store中的状态唯一的方式就是调用store.dispatch()。
  • 一个应用我们只应该创建一个store。
  • 使用combineReducers可以合并多个reducer为一个reducer。
  • reducer: 一个函数:入参(State,Action) => 返回新的状态树。
  • preloadedState: 初始时的 state,如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
  • enhancer: store增强器,可选的,可以集成一些第三方的插件。
  • 返回的store: 读取state、dispatch(action)、subscribe。

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 {

  // 一些数据类型校验
  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.'
    )
  }

  // 处理两个参数情况下:preloadedState为enhancer
  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.')
    }

    // 先处理enhancer有效的情况下,这里可以备注下跟applyMiddleware方法一样
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  // 后面流程就是没有enhancer的时候创建store
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 存储当前store中的reducer
  let currentReducer = reducer
  // 存储当前的state
  let currentState = preloadedState as S
  // 用一个数组存储当前的监听器
  let currentListeners: (() => void)[] | null = []
  // 下一次dispatch时候执行的监听器函数列表
  let nextListeners = currentListeners

  // 标示当前正在处理dispatch
  // 避免的常见场景有:
  // reducer里面执行dispatch
  // reducer里面执行subscribe、unsubscribe
  let isDispatching = false

  /**
   * 如果在reducer里面执行subscribe、unsubscribe
   * 那么会导致对listeners存在共同修改的风险
   * 因此currentListeners、nextListeners ensureCanMutateNextListeners 
   * 就是用来解决这个问题的
   * ensureCanMutateNextListeners就是为了拷贝一份数据而已,
   * 隔离subscribe、unsubscribe与dispatch操作不同的数据源
   */
  function ensureCanMutateNextListeners() {
    // 从currentListeners拷贝一份到nextListeners
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 从状态树读取状态
   */
  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
  }

  // 订阅监听器回调
  function subscribe(listener: () => void) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    // subscribe、unsubscribe内也不允许执行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()

    // 每次在listener回调执行订阅(subscribe)操作的一个新listener
    // 不会在此次正在进行的dispatch中调用,因为dispatch操作的是currentListeners
    // 它只会在下一次dispatch中调用,因为subscribe、unsubscribe操作的是nextListeners
    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
    }
  }

  // 接受action,生成新的state,执行订阅的回调
  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?'
      )
    }

    // reducer里面不允许再次执行dispatch, 会有性能问题 不断渲染listeners
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // combineReducers返回的是一个函数
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // dispatch从currentListeners获取数据操作
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   *
   * 多用于动态替换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.')
    }

    // TODO: do this more elegantly
    // 替换currentReducer
    ;((currentReducer as unknown) as Reducer<
      NewState,
      NewActions
    >) = nextReducer

    // 手动dispatch: 后面会用新的reducer来初始化state产生新的state
    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
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      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
      }
    }
  }

  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
}

所以如果我们使用createStore(reducer, state, enhancer)来创建store,那么其实会调用两次createStore。一次带enhancer,一次不带enhancer,后面会解释为什么。

applyMiddleware.ts

讲到applyMiddleware方法,我们需要了解Redux是支持两种方式来扩展。

  • middleware中间件
  • enhancer增强器

关于Redux的单向数据流,我们可以先看过这样一张图,留个印象。


//  创建store增强器,返回StoreEnhancer
export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {

  // applyMiddleware 返回一个函数。

  // 然后我们在回想之前createStore中如下代码。
  // 所以如果创建store时候带有enhancer那么会先创建store,然后把store往中间件中传递
  /**
   * createStore中的源码
   * return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
   */
  return (createStore: StoreEnhancerStoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    preloadedState?: PreloadedState<S>
  ) => {
    // 用传进来的reducer, preloadedState创建store   
    // 这里调用栈回再次回到createStore函数
    const store = createStore(reducer, preloadedState)

    // 一个临时dispatch定义
    let dispatch: Dispatch = () => {
      // 改造之前被调用提示错误
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    // middleware约定的中间件签名 ({ getState, dispatch }) => next => action
    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 得到所有改造后的中间件函数
    // 类似所有中间件函数到了这一步 是这样一个函数:next => action
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose方法的作用是,例如这样调用:
    // compose(func1,func2,func3)
    // 返回一个函数: (...args) => func1( func2( func3(...args) ) )
    // 这里的store.dispatch 就是中间件签名的next
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    // 覆盖store本身的dispatch方法
    return {
      ...store,
      dispatch
    }
  }
}

这里有几个非常重要的概念。

  • Redux middleware约定的中间件签名 ({ getState, dispatch }) => next => action
  • 理解compose函数的作用

applyMiddleware中如下源码就解释下为什么是这个样子。


// middleware约定的中间件签名 ({ getState, dispatch }) => next => action
    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 得到所有改造后的中间件函数
    // 类似所有中间件函数到了这一步 是这样一个函数:next => action
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose方法的作用是,例如这样调用:
    // compose(func1,func2,func3)
    // 返回一个函数: (...args) => func1( func2( func3(...args) ) )
    // 这里的store.dispatch 就是中间件签名的next
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    // 覆盖store本身的dispatch方法
    return {
      ...store,
      dispatch
    }

这里我们可以对比redux-thunk源码来理解


function createThunkMiddleware(extraArgument) {

  return function({ dispatch, getState }) { // 这是中间件函数本身
      //参数是store中的dispatch和getState方法 即来自middlewareAPI传入的

      return function(next) { // 这是改造后的中间件函数 即上述的 chain
          //参数next是被当前中间件改造前的dispatch 即store.dispatch

          return function(action) { // 这改造后的dispatch方法
              if (typeof action === 'function') {
                // action就是如下示例的 getMenusByRoleId()返回的函数
                //如果action是一个函数,就调用这个函数,并传入参数给函数使用
                return action(dispatch, getState, extraArgument);
              }

              //否则调用用改造前的dispatch方法
              return next(action);
          }
      }
  }
}

异步action的调用


// 一个异步action创建函数
export function getMenusByRoleId(roleId: string) {
  return (dispatch: Dispatch<AnyAction>) => {
    return $http
      .fetchMainMenu({ roleId })
      .then(res => {
        const { result } = res
        dispatch(updateUserMenus(result && Array.isArray(result) ? result : []))
      })
      .catch(e => console.error(e))
  }
}
// 业务中执行异步action
dispatch(getMenusByRoleId('xxx'))

compose.ts

这个是非常重要的一个工具函数,代码非常简洁,主要利用的是数组的API reduce。


// 把多个函数组合为一个函数调用
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)))
}

光看上述代码有点难理解,我们可以看一个示例。

function compose(...funcs) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

const f1 = (a) => {
  console.log("f1" + a);
  return "f1" + a;
};
const f2 = (a) => {
  console.log("f2" + a);
  return "f2" + a;
};
const f3 = (a) => {
  console.log("f3" + a);
  return "f3" + a;
};

var a = compose(f1, f2, f3)("xyz");
console.log("a answer is: ", a);
/**
 *
  f3xyz
  f2f3xyz
  f1f2f3xyz
  a answer is:  f1f2f3xyz
 */

这样子就很好理解了


compose(func1,func2,func3)
返回一个函数: (...args) => func1( func2( func3(...args) ) )

combineReducers.ts

随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。


function getUndefinedStateErrorMessage(key: string, action: Action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'

  return (
    `Given ${actionDescription}, reducer "${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.`
  )
}

function getUnexpectedStateShapeWarningMessage(
  inputState: object,
  reducers: ReducersMapObject,
  action: Action,
  unexpectedKeyCache: { [key: string]: true }
) {
  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)) {
    const match = Object.prototype.toString
      .call(inputState)
      .match(/\s([a-z|A-Z]+)/)
    const matchType = match ? match[1] : ''
    return (
      `The ${argumentName} has unexpected type of "` +
      matchType +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  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.`
    )
  }
}

// 检查reducers里面的reducer接受一个未知的action以及初始化的state
// 是否还是可以返回有效的state
function assertReducerShape(reducers: ReducersMapObject) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${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(
        `Reducer "${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.`
      )
    }
  })
}


export default function combineReducers(reducers: ReducersMapObject) {
  // 获取reducers对象的key array
  const reducerKeys = Object.keys(reducers)
  // 存储有效的reducer
  const finalReducers: ReducersMapObject = {}
  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)

  // 缓存重复的key
  let unexpectedKeyCache: { [key: string]: true }
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  // 接受assertReducerShape校验的异常Error
  let shapeAssertionError: Error
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    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: StateFromReducersMapObject<typeof reducers> = {}
    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)
      // 需要特别注意state为undefined的情况
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 存储更新后的state
      nextState[key] = nextStateForKey
      // 每次循环对比新旧state
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length // 对比有没有新增或者删除reducer
    return hasChanged ? nextState : state
  }
}

这里我们主要需要了解combineReducers返回的是一个函数。这个函数会随着createStore方法调用而传入。然后在调用dispatch时候而产生新的state。

// combineReducers返回的是一个函数
currentState = currentReducer(currentState, action)

bindActionCreators.ts

在讲述这API之前,我们先看看它在业务中的常见使用。


const mapStateToProps = (state: any) => ({
  pageState: state.customerAcPageState
})

const mapDispatchToProps = (dispatch: Dispatch) =>
  bindActionCreators(
    {
      customerAcPageState
    },
    dispatch
  )
export default connect(mapStateToProps, mapDispatchToProps)(ManageCustomAct)

// 业务中直接调用
this.props.customerAcPageState({})

大致意思是:把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。


function bindActionCreator<A extends AnyAction = AnyAction>(
  // actionCreator: actor 创建函数
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  // 返回函数通过dispatch包装,可以直接调用
  return function (this: any, ...args: any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}

// 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。
// 同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
export default function bindActionCreators(
  // actionCreators: 一个 action creator,或者一个 value 是 action creator 的对象。
  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {
  // 如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
  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 = {}
  // 遍历对象,挨个取出通过dispatch包装并存储
  for (const key in actionCreators) {
    // 取出action creator
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

结束完。具体还有一些待梳理,包括react-redux、redux的一些优缺点。

ps: 推荐我的微信公众号:xyz编程日记。