映射类型:属性 到项目

mapped type: Property to item

考虑这段代码:

type A = { k: 'a.a', u: string } | { k: 'a.b', v: string };
type B = { k: 'b.a', w: number } | { k: 'b.b', x: number };

列表中的每个项目都有一个键k,它是唯一的。所以

type AandB = A | B;

产生整体联合。

我正在尝试创建这样的映射类型:

type Map = {
  [k in AandB['k']]: <the respective item in AandB> AandB[k] wont work.
}

如果应用,编译器应该验证这样的对象:

const m: Map = {
  "a.a": { k: 'a.a', u: 'test' }
  …
  …
  …
}

如何获得相应的物品?

这可以通过一些 Type Generic 技巧来实现:

// Get the type of a union based on the value (V) and lookup field (K)
type DiscriminateUnion<
  T,
  K extends keyof T,
  V extends T[K]
> = T extends Record<K, V> ? T : never;

然后:

type Map = {
  [k in AandB['k']]: DiscriminateUnion<AandB, 'k', k>;
};

那么,地图应该按照你指出的那样严格输入:


const map: Map = {
  'a.a': { k: 'a.a', u: 'example' },
  'b.a': { k: 'b.a', w: 0 },
  'b.b': { k: 'b.b', x: 1 },
  'a.b': { k: 'a.b', v: 'Example' }, // TS won't let you change the ids 
// or variable names unless they match the respective object key type.
};

为什么这样做有效?

重要的是要将联合类型理解为不仅仅是一种类型,而是多种类型的数组。

当进行以下比较时:

T extends Record<K, V>? T : never

我们实际上是将此联合的每种类型与以下三元(if 语句)进行比较。

由于 Record 表示一个对象,其中键的类型为 K,值的类型为 V,我们最终在 AandB 中对每种类型进行循环并且只返回 K: 'k' 等于指定键 V 的类型,可能是 a.a, b.b, etc,否则返回 never 如果至少返回一个元素,则忽略该类型。