Redux 源码分析

news2024/9/24 23:28:38

Redux 目录结构

redux                                                             
├─ .babelrc.js                                                    
├─ .editorconfig                                                   
├─ .gitignore                                                      
├─ .prettierrc.json                                               
├─ CHANGELOG.md                                                    
├─ CNAME                                                           
├─ CODE_OF_CONDUCT.md                                              
├─ CONTRIBUTING.md                                                 
├─ LICENSE-logo.md                                               
├─ LICENSE.md                                                     
├─ PATRONS.md                                                     
├─ README.md                                                       
├─ docs                                     // 文档
├─ errors.json                              
├─ logo                                       
├─ netlify.toml                                            
├─ package-lock.json                                         
├─ package.json                                         
├─ rollup.config.js                         // rollup 打包配置
├─ scripts                                
├─ src                                      // 源代码
│  ├─ applyMiddleware.ts          
│  ├─ bindActionCreators.ts       
│  ├─ combineReducers.ts          
│  ├─ compose.ts                  
│  ├─ createStore.ts              
│  ├─ index.ts                    
|  └─ types
│  └─ utils                                 // 一些工具方法
│     ├─ isPlainObject.ts         
│     ├─ kindOf.ts                
│     ├─ symbol-observable.ts     
│     └─ warning.ts               
├─ tsconfig.json                  
└─ website                                  // redux 首页网站

主要对src目录下文件进行分析,对于utils中的方法,有意思的也可以看看

index.ts

这个主要导出一些对象或者做运行环境检测的,没有特殊功能

createStore.ts

对于一个redux 应用,整个应用的状态存储在一棵state 树中,由store 维护这个状态树,并通过dispatch 进行修改。

首先看看state 的数据结构

/types/store.ts

  /**
   * 替换当前 store 用来计算最新 state 的 reducer 函数
   * 
   * app 如果实现了 code splitting,那可能要动态加载 reducer。
   * 如果对 redux 要实现热重载,也可能要用到这个方法
   *
   * @param nextReducer 返回新替换了 reducer 的 store
   */
  replaceReducer<NewState, NewActions extends Action>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext/**
 * store 是一个维护 app 状态树的对象
 * 一个 redux app 只能有一个 store,拆分和组合只能发生在 reducer 层面中
 *
 * @template S state 类型
 * @template action 类型
 * @template 来自 store enhancer 的 state 拓展
 * @template 来自 store enhancer 的 store 拓展
 */
export interface Store<
  S = any,
  A extends Action = AnyAction,
  StateExt = never,
  Ext = {}
> {
  /**
   * dispatch action。触发 state 更新的唯一方法
   * 
   * 创建 store 的时候要传入一个 reducer 函数,调 diapatch 函数的时候就会调那个 reducer 方法,
   * 并传入对应的 action
   * dispatch 方法会产生一个新的 state tree,且所有的监听者都会被通知
   * 
   * 基础实现仅支持普通js对象的 action,如果想要 dispatch promise、observable、thunk
   * 或其他什么东西要使用 middleware。举例,可以去看 redux-thunk 包的文档。
   * 但是使用 middleware 之后最终也会使用这个方法 dispatch 一个普通js对象 action
   *
   * @returns 图方便,返回传入的那个 actio
   */
  dispatch: Dispatch<A>

  /**
   * 获取 store 维护的 state
   */
  getState(): S

  /**
   * 添加一个变化监听者。
   * 任意 action 被 dispatch 的时候都会被调用,state 树的某个部分可能会被更新了。
   * 可以在传入的回调函数中调用 getState 来获取最新的 state 树。
   *
   * @returns 返回一个取消订阅的方法
   */
  subscribe(listener: () => void): Unsubscribe

  /**
   * 替换当前 store 用来计算最新 state 的 reducer 函数
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param nextReducer The reducer for the store to use instead.
   */
  replaceReducer<NewState, NewActions extends Action>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext

  /**
   * 观察者/响应式库的互操作点。
   * 
   * @returns {observable} 变化的最小 observable.
   * 详情可以查看 observable 提案:
   * https://github.com/tc39/proposal-observable
   */
  [Symbol.observable](): Observable<S>

因此对于 createStore 方法就是要返回一个这样的数据结构:

/**
 * 创建一个 Redux store 来持有整个 state 树。
 * 唯一改变 store 中数据的方法是对它调用 `dispatch()`。
 *
 * app 中应该只有单一的 store。为了弄清楚 state 树如何针对 state 树的不同部分进行响应,
 * 也可以使用 `combineReducers` 来将多个 reducer 组合到单一的 reducer 函数中去
 *
 * @param reducer 一个返回下一个 state 树的函数,需要接收当前的 state 树和要处理的 action。
 *
 * @param preloadedState 初始 state。
 * 你可以选择指定它以在通用 app 中从服务器还原状态,或还原以前序列化的用户会话。
 * 如果你使用 `combineReducers` 来生成根 reducer 函数,
 * 那么该函数必须是与 `combineReducers` 的键具有相同形状的对象。
 *
 * @param enhancer store enhancer。 
 * 你可以选择指定它以使用第三方功能(如 middleware、时间旅行、持久性等)增强 store。
 * Redux附带的唯一 store enhancer 是 `applyMiddleware()`。
 *
 * @returns 一个 redux store,让你可以读取 state,dispatch action,并订阅 state 变化
 */
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 {
  // 做一些参数和运行环境的校验,省略
  
  // 如果 enhancer 是函数,则返回 enhancer 加强 store,省略

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  /**
   * 对 currentListeners 做一次浅拷贝,
   * 使得我们在 dispatch 过程中可以使用 nextListeners 作为临时的 list
   * 
   * 这一步防止了任何 数据消费者 在 dispatch 过程中
   * 调用 subscribe/unsubscribe 出现的错误,
   */
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 读取 store 管理的 state 树。
   *
   * @returns 当前 app 的 state 树
   */
  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
  }

  /**
   * 添加一个 listener。在 action 被 dispatch 的时候,
   * 或 state tree 中的某些部分可能改变时被随时调用,
   * 你可以再回调函数中调用 `getState()` 来读取当前的 state 
   * 
   * 你可以从一个 listener 调用 `getState()`,但是伴随有以下警告:
   * 
   * 1. 每个订阅都是在每个 `dispatch()` 调用之前的快照。
   * 如果你在 listener 正在被调用的时候 subscribe 或 unsubscribe,那么对于当前的 `dispatch()`
   * 流程来说根本没用。
   * 但是,下一次的 `dispatch()` 调用,无论是不是嵌套的调用,都会带上最新的 订阅 list 的快照。
   * 
   * 2. listener 不应该盯着所有的 state 更改,因为在 listener 被调用之前 state 可能会
   * 在嵌套 `dispatch()` 过程中被多次更新。
   * 但是,可以保证在 `dispatch()` 启动之前注册的所有 listener 保证以最新状态调用。
   *
   * @param listener 每次调用 dispatch 的时候都被触发的回调.
   * @returns 一个移除此 listener 的函数.
   */
  function subscribe(listener: () => void) {
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
          listener
        )}'`
      )
    }

    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 也用的是不可变更新方式,
     * 以免在正在 dispatch 的时候添加或者移出 listener 发生错误
     * 也就是说,只有在对应 action 被 dispatch 之前添加或者移除 listener 才有效
     */
    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
    }
  }

  /**
   * dispatche 一个 action。这是改变 state 的唯一方法。
   *
   * 用来创建 store 的 `reducer` 函数,将会根据当前的 state 树和给定 action被调用。
   * 它的返回解雇将会被视作 **下一个** state,并且会通知 listener。
   * 
   * 基本实现仅仅支持普通的 action 对象。日过想要 dispatch 一个 Promise,Observable,
   * thunk 等,你得使用对应的 middleware 封装 store 创建函数。例如,可以去看 
   * `redux-thunk` 包的文档。虽然 middleware 最终也是通过这个方法 dispatch 一个普通对象。
   *
   * @param action 一个用来表示“发生什么”的普通对象。 
   * 这样 action 能被序列化,你就可以记录和重现用户的会话,或者使用 `redux-devtools 完成时间旅行调试。
   * 一个 action 必须有 `type` 属性,且不能为 `undefined`。
   * 使用字符串常量来定义这个属性是个好主意
   *
   * @returns 为了方便,返回你 dispatch 的那个原对象
   *
   * 注意到,如果你使用一个通用 middleware,他可能会封装 `dispatch()` 从而返回一些其他东西
   * (比如,返回一个 Promise 你能 await)。
   */
  function dispatch(action: A) {
    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 = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 依次通知 listener
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * 替换当前 store 使用的 reducer 来计算 state。
   * 
   * 可能你的 app 需要代码分割并动态加载一些 reducer,也可能要实现一些 redux 热重载机制
   *
   * @param nextReducer 给 store 替换的那个 reducer
   * @returns 替换过 reducer 的同一个 store 实例
   */
  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. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }

    // TODO:现在的实现不够优雅
    ;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer

    // 这个 action 和 ActionTypes.INIT 效果一样
    // 新的和旧的 rootReducer 中存在的任何 Reducer 都将接收以前的状态。
    // 这将使用旧 state 树中的任何相关数据有效地填充新 state 树。
    dispatch({ type: ActionTypes.REPLACE } as A)
    // 通过强转类型为新 store 来改变 store 类型
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }

  /**
   * 观察式/响应式库的交互切点
   * @returns state 变化的最小可观察。
   * 有关更多信息,请参阅可观察提案:
   * https://github.com/tc39/proposal-observable
   */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /**
       * 最小的 observable 订阅的方法
       * @param observer 可以被用作 observer 的任何对象
       * observer 对象都应该有 `next` 方法。
       * @returns 一个具有 `unsubscribe` 方法的对象,这个对象可以用来从 store 取消订阅 observable,
       * 并防止进一步从 observable 获得值
       */
      subscribe(observer: unknown) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError(
            `Expected the observer to be an object. Instead, received: '${kindOf(
              observer
            )}'`
          )
        }

        function observeState() {
          const observerAsObserver = observer as Observer<S>
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

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

  // 当 store 被初始化以后,一个 "INIT" action 就会被 dispatch,这样每个 reducer 返回他们的初始 state。
  // 这有效地填充了初始 state 树。
  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
}

bindActionCreators.ts

为了使用方便,redux 提供了一个根据 action 的 key 返回一个封装了 disptch 方法的函数bindActionCreators 。相当于不用手动dispatch 了。

这个方法不是必须的,手动调dispatch(createAction()) 也是可以的。

/**
 * 给单个 action 绑定 dispatch 函数
 * @param actionCreator 一个 action creator 函数
 * @param dispatch store.dispatch 方法
 * @returns 返回一个函数,这个函数直接调用就相当于 dispatch 一个对应的 action
 */
function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  return function (this: any, ...args: any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}
/**
 * 把一个值都是 action creator 的对象转化成另一个有着相同键的对象,
 * 但是每个函数都封装了一个 `dispatch` 调用进去,所以都能被直接调用。
 * 这是个简便方法,你可以自己调用 `store.dispatch(MyActionCreators.doSomething())`
 * 
 * 为了方便,你也能传一个 action creator 进去作为第一个参数,
 * 返回值得到了一个 封装了 dispatch 的函数
 *
 * @param actionCreators 一个值都是 action creator 函数的对象。
 * 一个简单的获得方法就是使用 ES6 语法 `import * as`,
 * 你也可以传单个函数。
 *
 * @param dispatch Redux store 中可用的 `dispatch` 函数。
 *
 * @returns 模仿原始对象的对象,但是每个 action creator 都封装进了 `dispatch` 调用。
 * 如果你传入一个函数比如 `actionCreators` ,返回值仍然是单个函数。
 */
export default function bindActionCreators(
  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, but instead received: '${kindOf(
        actionCreators
      )}'. ` +
        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const boundActionCreators: ActionCreatorsMapObject = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

applyMiddleware.ts

顾名思义,就是将 middleware 应用到 store 上,来对 store 或者其他的一些东西进行增强。

/**
 * 创建一个 store enhancer 来将 middleware 应用到 redux store 的 dispatch 方法。
 * 这对于多种任务来说非常方便,例如,以简洁的方式表达异步操作,或记录每个 action payload。
 *
 * 看 `redux-thunk` 包可以作为一个 redux middleware 的例子
 * 因为 middleware 可能是异步的,所以这应该是组合链中的第一个 store enhancer。
 *
 * 注意每个 middleware 都会有 `dispatch` 和 `getState` 函数作为具名参数。
 *
 * @param middlewares 需要应用的 middleware 链
 * @returns 一个应用 middleware 的 store enhancer
 *
 * @template Ext middleware 添加的 dispatch 签名
 * @template S middleware 支持的 state 类型。
 */
export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {
      const store = createStore(reducer, preloadedState)
      let dispatch: Dispatch = () => {
        throw new Error(
          'Dispatching while constructing your middleware is not allowed. ' +
            'Other middleware would not be applied to this dispatch.'
        )
      }

      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      /**
       * chain的结构: Array< (next: Dispatch<AnyAction>) => (action: AnyAction) => any >
       * compose 的作用:compose(A, B, C, arg) === A(B(C(arg))) 最后一个参数是 store.dispatch,
       *    使得 middleware 依次执行,最后执行 store.dispatch。  柯里化,串联
       * compose 传入的函数列表后,新生成的 dispatch 调用的时候相当于依次调用这些 middleware 后
       *    最后调用原生的 store.dispatch
       */
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }
    }
}

applyMiddeware 方法调用后生成一个StoreEnhancer ,可以查看其类型定义:

/**
 * store enhancer 是一个高阶函数,将一个 store creator 组装成一个新的,增强过的
 * store creator。和 middleware 相似,以组合式方式更改 store。
 */
export type StoreEnhancer<Ext = {}, StateExt = never> = (
  next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
  
/** 增强过的 storeCreator 类型 */
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
  S = any,
  A extends Action = AnyAction
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext

因此,applyMiddleware 的使用方法大概是:

applyMiddleware(thunkMiddleware,middleware1,middleware2,...)({ reducer: ..., preloadedState: {} });

对于一个 middleware 来说,必须要实现 MiddlewareAPI,其类型定义如下:

export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
  dispatch: D
  getState(): S
}

export interface Middleware<
  _DispatchExt = {}, // TODO: remove unused component (breaking change)
  S = any,
  D extends Dispatch = Dispatch
> {
  (api: MiddlewareAPI<D, S>): (
    next: D
  ) => (action: D extends Dispatch<infer A> ? A : never) => any

以redux-thunk 的实现为例(https://github.com/reduxjs/redux-thunk/blob/master/src/index.ts),实现一个middleware 需要传入的dispatch 方法和getState 方法作为参数:

function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  // Standard Redux middleware definition pattern:
  // See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      // The thunk middleware looks for any functions that were passed to `store.dispatch`.
      // If this "action" is really a function, call it and return the result.
      if (typeof action === 'function') {
        // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
        return action(dispatch, getState, extraArgument)
      }

      // Otherwise, pass the action down the middleware chain as usual
      return next(action)
    }
  return middleware
}

applyMiddleWare方法在生成强化后的dispatch方法的时候,用到了一个compose方法,该方法是将一个函数列表柯里化。

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // 推断参数类型,使其在推断中可用
    return <T>(arg: T) => arg
  }

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

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

这里使用柯里化主要是:1、代码可读性;2、易于增删中间件

贴一个 ChatGPT 的回答:

combineReducers.ts

combineReducers方法是在 Redux 库中提供的一种工具。它的作用是合并多个 reducer 函数,并将它们合并成一个单一的 reducer 函数,以便在 Redux 中管理多个不同的数据状态。好处是能够让你的应用更加模块化和结构化,并且更加容易维护和管理。

/**
 * 将多个 Reducer 函数整合为一个 Reducer,以便在 Redux 中管理多个不同的数据状态
 * 它将调用所有的 子 reducer,并且把调用结果汇集到一个 单一的 state 对象中,
 * 这个 state 对象的键就是传入的 reducer 函数名。
 *
 * @template S 组合 state 对象的类型。
 *
 * @param reducers 一个值对应的 reducer 函数要被合并为一个的对象。
 *   一个简便方法是使用 ES6  `import * as reducers` 语法来获取。
 *   reducers 对于任何 action 可能都不会返回 undefined。
 *   相反的,他们需要返回自己的 初始 state。
 *   相反,如果传递给它们的 state 是 undefined,它们应该返回初始 state,
 *   而对于任何无法识别的操作,则返回当前 state。
 *
 * @returns 一个 reducer 函数,它调用传递对象内的每个 reducer,
 *   并构建具有相同形状的 state 对象。
 */
export default function combineReducers(reducers: ReducersMapObject) {
  // 所有 reducer 的 key
  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}"`)
      }
    }

    // 仅保留 reducer 对象中 值为 function 的键值对
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 这用于确保我们不担心使用到相同的键。
  let unexpectedKeyCache: { [key: string]: true }
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  /**
   * 校验 reducer 是否都符合规定,见 assertReducerShape 方法(后面介绍)
   * 1. 能不能接受 init 的 action
   * 2. 能不能处理未知的 action
   */
  let shapeAssertionError: unknown
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    // 存在不符合规范的 reducer,直接抛出错误
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      // getUnexpectedStateShapeWarningMessage 只是一个生成 warning 消息的方法
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState: StateFromReducersMapObject<typeof reducers> = {}

    /**
     * 遍历所有的 reducer 并分别执行,将计算出的 state 组合起来生成一个大的 state
     * 因此对于任何 action,redux 都会遍历所有的 reducer
     */
    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
  }
}

assertReducerShape方法专门用来检查 reducer 是不是合法的,不合法则抛出错误:

  • 试着使用 INIT Action 调用一下 Reducer,看是否能够得到一个初始状态

  • 试着处理一个未知的 Action 类型

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

其他——utils下一些方法

  • isPlainObject

/**
 * 通过 {} 或者 new Object() 方式创建的对象是纯粹对象
 * isPlainObject 函数的功能的判断依据与对象使用什么方式创建无关,而与的函数原型是否 === Object.prototype 有关
 */
export default function isPlainObject(obj: any): boolean {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  // 当 proto === Object.prototype 时会跳出循环
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  // 判断原型链的 首是否等于尾
  return Object.getPrototypeOf(obj) === proto
}
  • kindOf返回变量或对象的类型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

列线图工具_Nomogram

定义 列线图是一种相对传统的分析方法&#xff0c;用于展示自变量和因变量的线性关系&#xff0c;及其特征的重要程度。 现在用SHAP&#xff0c;和机器学习库中的 Feature importance 工具可以实现类似甚至更好效果。不过很多传统的研究领域比较认这种方法。 列线图工具建立在…

什么是相机标定

1. 相机标定的定义及作用 相机标定是指借助标定板来计算单个或多个相机的内参、外参和镜头畸变参数。 作用&#xff1a; 将畸变的图像恢复为正常的图像&#xff0c;为后续进行拼接、SLAM等奠定基础。 多相机标定可以将所有相机输出变换到同一个坐标系。 相机标定是三维视觉…

适用于 Windows 11/10/8/7 的 10 大数据恢复软件分享

适用于 Windows 11/10/8/7 的 最佳数据恢复软件综述。选择首选的专业数据/文件恢复软件&#xff0c;轻松恢复丢失的数据或删除的照片、视频等文件、SSD、外接硬盘、USB、SD卡等存储设备中的文件等。流行的sh流行的数据恢复软件也包括在内。 10 大数据恢复软件分享 为了帮助您恢…

美赛Day2

3 熵权法 相对客观的权重计算方法&#xff08;层次分析法都是自己瞎填&#xff09; 3.1 原理 指标的变异程度越小&#xff0c;反应的信息越少&#xff0c;对应的权值越低。 一个事件的信息量&#xff1a;I(x) -ln(p(x)) 信息熵&#xff1a;对X可能发生的所有情况的信息量的…

仿真与测试:通过Signal Builder模块生成输入信号

本文研究通过Signal Builder模块生成输入信号的方法。 文章目录1 生成输入信号2 仿真过程2.1 搭建被测模型2.2 搭建Signal Builder输入模块2.3 配置仿真log及仿真3 总结1 生成输入信号 在汽车的电控软件开发中&#xff0c;经常会在Simulink模型内部进行单元测试。单元测试的本…

FlexRay™ 协议控制器 (E-Ray)-04

网络管理 累积的网络管理 (NM) 向量位于网络管理寄存器 1 到网络管理寄存器 3 (NMVx (x = 1-3)) 中。【The accrued Network Management (NM) vector is located in the Network Management Register 1 to Network Management Register 3 (NMVx (x = 1-3)).】 网络管理向量 x…

『前端必备』本地数据接口—json-server

文章目录前言一、json-server简介二、起步三、使用步骤前言 Ajax 是前端必学的一个知识点&#xff0c;但刚接触 Ajax 的同学可能会因为没接口测试而烦恼。本文 入门篇 会花你10分钟解决 没接口测试 这个烦恼&#xff0c;而且不需要你具备后端知识。 一、json-server简介 json…

一个测试人员,在现阶段的环境下如何在测试行业发展和自我价值。

前言周末和几个测试圈子里的大佬饭局上聊了一些职场和测试职业发展相关的话题&#xff0c;我将聊天的内容做了整理和阐述。。朋友圈有测试同学对这篇文章提了比较深刻的建议&#xff0c;下面是他的评价和建议&#xff1a;评价&#xff1a;据说是大佬饭桌总结&#xff0c;有两点…

【论文精读】Deep Residual Learning for Image Recognition

1 Degradation Problem&#x1f4a6; 深度卷积神经网络在图像分类方面取得了一系列突破。深度网络自然地将低/中/高级特征和分类器以端到端的多层方式集成在一起&#xff0c;特征的“层次”可以通过堆叠层数(深度)来丰富。最近的研究揭示了网络深度是至关重要的&#xff0c;在具…

Netty零拷贝机制

Netty零拷贝机制一&#xff1a;用户空间与内核空间二&#xff1a;传统IO流程三&#xff1a;零拷贝常见的实现方式1. mmap write2. sendfile四&#xff1a;Java中零拷贝五&#xff1a;Netty 中如何实现零拷贝1. CompositeByteBuf 实现零拷贝2. wrap 实现零拷贝3. slice 实现零拷…

Pytorch 混合精度训练 (Automatically Mixed Precision, AMP)

Contents混合精度训练 (Mixed Precision Training)单精度浮点数 (FP32) 和半精度浮点数 (FP16)为什么要用 FP16为什么只用 FP16 会有问题解决方案损失缩放 (Loss Scaling)FP32 权重备份黑名单Tensor CoreNVIDIA apex 库代码解读opt-level (o1, o2, o3, o4)apex 的 o1 实现apex …

Docker安全

容器的安全性问题的根源在于容器和宿主机共享内核。如果容器里的应用导致Linux内核崩溃&#xff0c;那么整个系统可能都会崩溃。 与虚拟机是不同的&#xff0c;虚拟机并没有与主机共享内核&#xff0c;虚拟机崩溃一般不会导致宿主机崩溃 一、Docker 容器与虚拟机的区别 1、隔…

Redis配置哨兵模式

Redis配置哨兵模式 ​ ​ 主从复制模式&#xff0c;它是属于 Redis 多机运行的基础&#xff0c;但这种模式本身存在一个致命的问题&#xff0c;当主节点奔溃之后&#xff0c;需要人工干预才能恢复 Redis 的正常使用。 我们需要一个自动的工具——Redis Sentinel&#xff08;…

Win11浏览器无法上网,秒杀网上99.9%教程—亲测完胜

前言 例如&#xff1a;网上的教程 列如&#xff1a; 关闭代理服务器、QQ微信可以登录&#xff0c;但浏览器无法上网、Win11、Win10无法上网、重启网络、重启电脑、去掉代理服务器等等。 一系列教程&#xff0c;要多鸡肋就多鸡肋。 我是用我2020年在CSDN上发布的第一篇文章&…

自动驾驶规划 - Apollo Lattice Planner算法【1】

文章目录Lattice Planner简介Lattice Planner 算法思路1. 离散化参考线的点2. 在参考线上计算匹配点3. 根据匹配点&#xff0c;计算Frenet坐标系的S-L值4. parse the decision and get the planning target5. 生成横纵向采样路径6. 轨迹cost值计算&#xff0c;进行碰撞检测7. 优…

Fluent Python 笔记 第 8 章 对象引用、可变性和垃圾回收

本章先以一个比喻说明 Python 的变量&#xff1a;变量是标注&#xff0c;而不是盒子。如果你不知道引用式变量是什么&#xff0c;可以像这样对别人解释别名。 然后&#xff0c;本章讨论对象标识、值和别名等概念。随后&#xff0c;本章会揭露元组的一个神奇特性&#xff1a;元…

2023 年腾讯云服务器配置价格表出炉(2核2G/2核4G/4核8G/8核16G、16核32G)

腾讯云轻量应用服务器为轻量级的云服务器&#xff0c;使用门槛低&#xff0c;按套餐形式购买&#xff0c;轻量应用服务器套餐自带的公网带宽较大&#xff0c;4M、6M、7M、10M、14M及20M套餐可选&#xff0c;如果是云服务器CVM这个带宽价格就要贵很多了。 1、轻量应用服务器优惠…

openpyxl表格的简单实用

示例:创建简单的电子表格和条形图 在这个例子中,我们将从头开始创建一个工作表并添加一些数据,然后绘制它。我们还将探索一些有限的单元格样式和格式。 我们将在工作表上输入的数据如下: 首先,让我们加载 openpyxl 并创建一个新工作簿。并获取活动表。我们还将输入我们…

java ArrayList

目录 一.简单介绍 二.ArrayList的底层结构 2.1ArrayList的底层结构和操作分析 2.ArrayList 底层源码分析 三.ArrayList 方法 四.代码使用方法 一.简单介绍 ArrayList 类是一个可以动态修改的数组&#xff0c;与普通数组的区别就是它是没有固定大小的限制&#xff0c;我们…

Mac系统Mysql的8.0.22版本安装笔记和密码重置修改密码等问题方法

忘记密码官网教程地址&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/resetting-permissions.html 5.7数据库安装指南参考&#xff1a;https://jingyan.baidu.com/article/fa4125ac0e3c2928ac709204.html 初次安装8.0.22遇到许多坑&#xff0c;密码修改失败&#xff1b…