Redux的打字稿类型中的CombinedState类型的原因

时间:2019-09-22 07:07:14

标签: typescript redux

Redux具有以下类型:

declare const $CombinedState: unique symbol

/**
 * State base type for reducers created with `combineReducers()`.
 *
 * This type allows the `createStore()` method to infer which levels of the
 * preloaded state can be partial.
 *
 * Because Typescript is really duck-typed, a type needs to have some
 * identifying property to differentiate it from other types with matching
 * prototypes for type checking purposes. That's why this type has the
 * `$CombinedState` symbol property. Without the property, this type would
 * match any object. The symbol doesn't really exist because it's an internal
 * (i.e. not exported), and internally we never check its value. Since it's a
 * symbol property, it's not expected to be unumerable, and the value is
 * typed as always undefined, so its never expected to have a meaningful
 * value anyway. It just makes this type distinquishable from plain `{}`.
 */
export type CombinedState<S> = { readonly [$CombinedState]?: undefined } & S

对于这个符号和类型的用途,我有点头绪。

combineReducers

中的示例用法
export default function combineReducers<S>(
  reducers: ReducersMapObject<S, any>
): Reducer<CombinedState<S>>
export default function combineReducers<S, A extends Action = AnyAction>(
  reducers: ReducersMapObject<S, A>
): Reducer<CombinedState<S>, A>
export default function combineReducers<M extends ReducersMapObject<any, any>>(
  reducers: M
): Reducer<
  CombinedState<StateFromReducersMapObject<M>>,
  ActionFromReducersMapObject<M>
>
export default function combineReducers(reducers: ReducersMapObject) {
  const reducerKeys = Object.keys(reducers)
  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]
    }
  }

评论说它曾经与{}或空类型区分开来,但是我看不到任何地方检查它,甚至在评论中说我们内部从不检查它的值,为什么呢?甚至在那里除了让我这样的人迷惑之外?

1 个答案:

答案 0 :(得分:2)

正如评论中提到的打字稿是鸭式打字,这意味着您可以将看似无关的类型分配给另一个:

type A = { foo: string, baz?: string }
type B = { foo: string, goo?: number }

declare let a: A;
declare let b: B;

// Unrelated types, structurally compatible, so assignable
a = b;
b = a;

Play

此行为称为structural typing,与其他强类型语言(例如Java或C#)的工作方式不同。更多传统语言使用nominal typing,其结构无关紧要,并且除非有某些继承关系,否则A不能分配给B

要模拟类似于打字稿中标称键入的内容,我们可以使用标称行为的专用字段或unique symbol。这意味着结构无关紧要,在确定兼容性时,只有两个unique symbol来自相同的定义才是兼容的。

这意味着对于CombinedState,类型系统将能够保证,如果类型为T extends { [$CombinedState]: undefined },则它必须来自combineReducers(或者至少是它的实例化) CombinedState)。这是有保证的,因为客户端无法访问$CombinedState,因此只能用于库中。

为什么这很重要?很可能是由于PreloadedState

/**
 * Recursively makes combined state objects partial. Only combined state _root
 * objects_ (i.e. the generated higher level object with keys mapping to
 * individual reducers) are partial.
 */
export type PreloadedState<S> = Required<S> extends {
  [$CombinedState]: undefined
}
  ? S extends CombinedState<infer S1>
    ? {
        [K in keyof S1]?: S1[K] extends object ? PreloadedState<S1[K]> : S1[K]
      }
    : never
  : {
      [K in keyof S]: S[K] extends object ? PreloadedState<S[K]> : S[K]
    }

PreloadedState中,SCombinedState的事实在条件类型中触发了不同的行为。