根据映射键从接口映射中选择类型

Picking a type from an interface map based on map key

我想编写一个通用的创建方法来生成在地图中输入的不同对象。问题是 typescript 将所有接口混合在一起,而不是只在接口映射中选择一个:

interface A  {
  a: string;
}

interface B {
  b: string;
}

interface ABMap {
  a: A;
  b: B;
}

function create<ID extends keyof ABMap>(id: ID): ABMap[ID] { // this is now combined A & B instead of A | B
  if (id === 'a') {
    return {a: 'a'}; // error a is missing b key 
  } else if (id === 'b') {
    return {b: 'b'};
  }
}

这里有两个问题。

首先,TypeScript 并没有基于类型保护来缩小泛型的定义。这是一种已知和预期的行为,尽管它可能令人沮丧。当你检查 if (id === 'a') 时,typescript 知道变量 id 的值是 A 类型。然而,它并没有将通用范围缩小到 A。泛型 ID 是联合 A | B 并且变量 idA 是完全有效的。因此,当该检查通过时,我们知道 ID 必须包含 A,但我们不知道它是“A 且仅包含 A”。

第二个问题是为什么类型 ABMap[ID] 缩小为 A & B 而不是 A | B。那个我无法解释。

有(至少)三个解决方案。

  1. 您可以做您所做的,将 return 类型扩展为 ABMap[keyof ABMap] 又名 A | B

  2. 如果您正在处理一小组配对,例如本例中的两个配对,您可以通过函数重载关联输入和输出:

function create(id: 'a'): ABMap['a'] 
function create(id: 'b'): ABMap['b'] 
function create(id: keyof ABMap): ABMap[keyof ABMap] {
  if (id === 'a') {
    return {a: 'a'};
  } else {
    return {b: 'b'};
  } 
}
  1. 您可以保留您的泛型并通过使用 as 关键字告诉打字稿您正在 return 实际上是正确的类型。
function create<ID extends keyof ABMap>(id: ID): ABMap[ID] {
  if (id === 'a') {
    return {a: 'a'} as ABMap[ID];
  } else {
    return {b: 'b'} as ABMap[ID];
  }
}

Playground Link