从Typescript中的嵌套类型推断正确的对象

ecbunoof  于 2022-12-05  发布在  TypeScript
关注(0)|答案(1)|浏览(175)

我试图让Typescript在switch case中推断正确的对象,我很难理解为什么我的场景不工作(与对象),但当它是联合类型时却能按预期工作。
从操场链接:

interface Base {
  id: number;
  name:  "a" | "b" | "c" | "d"; // This is type-guarding correctly
  type: {
    id: number;
    name: "a" | "b" | "c" | "d"; // This is NOT type-guarding correctly
  }
}

interface A extends Base {
  name: "a";
  type: {
    id: number;
    name: "a";
  }
  a: {
    b: number;
  }
}

interface B extends Base {
  name: "b";
  type: {
    id: number;
    name: "b";
  }
  b: {
    a: number;
  }
}

interface Generic extends Base {
  name: "c" | "d";
  type: {
    id: number;
    name: "c" | "d";
  }
}

type C = A | B | Generic

function test(t: C) {
  // If you switch the switch case to 't.name'
  switch(t.type.name) {
  // switch(t.name) {
    case "a":
      // This will be properly type-guarded as it will infer A
      console.log(t.a)
      break;
    case "c":
      console.log(t.name);
      break;
    case "b":
      // This will be properly type-guarded as it will infer B
      console.log(t.b);
      break;
  }
}

我有一个对象,它可以根据type.name的值具有不同的属性。我希望在switch case中检查该type.name,并让typescript了解我可以基于此访问哪些属性。根据示例,如果我在name上执行switch case,它将按预期工作。
Playground链接
除了使用as或平面化对象之外,我还有哪些其他选项?

dsf9zpds

dsf9zpds1#

TypeScript不支持“嵌套”区分联合体,在这种情况下,您使用子属性来区分成员。请参见microsoft/TypeScript#18758。因此,虽然C * 是 * 一个以name作为判别式的区分联合体,但您不能使用type.name作为判别式。因此,您几乎不能使用任何设计用于处理区分联合体的机制。例如在判别特性上使用switch
如果你想让编译器对子属性进行类型保护,这样你就不必使用类型Assert或改变你的对象结构,那么我能想到的唯一选择就是编写一个用户定义的类型保护函数来为你执行收缩。它可以看起来像这样:

function hasNestedTypeName<
  T extends { type: { name: string } },
  K extends string
>(t: T, k: K): t is
  T extends { type: { name: infer N } }
  ? K extends N ? T : never
  : never {
  return t.type.name === k
}

如果你调用hasNestedTypeName(t, k),它返回true,那么编译器会将t的范围缩小到type.name属性可以接受k作为值的联合成员。else链而不是switch,因为boolean返回类型保护函数的类型。如下所示:

function test(t: C) {
  if (hasNestedTypeName(t, "a")) {
    // function hasNestedTypeName<C, "a">(t: C, k: "a"): t is A
    t // A
    console.log(t.a)
  } else if (hasNestedTypeName(t, "c")) {
    // function hasNestedTypeName<B | Generic, "c">(t: B | Generic, k: "c"): t is Generic
    t // Generic
    console.log(t.name)
  } else if (hasNestedTypeName(t, "b")) {
    t // B 
    console.log(t.b)
  }

这和你的版本差不多(实际上很难让hasNestedTypeName(t, "c")按预期工作,因为如果它返回false,编译器会假设t * 不是 * Generic,但这不一定。我会远离判别式本身就是一个并集的判别式并集,因为它可能会做一些奇怪的事情)。
是否值得这样的改变取决于用例。我个人建议重构对象形状,使判别式处于顶级是最好的方法,因为它利用了语言的优点,而不是试图绕过它的缺点。
Playground代码链接

相关问题