打字稿根据对象值改变类型结构

时间:2021-06-22 11:56:04

标签: typescript discriminated-union type-narrowing

我有一个 API,它返回大量数据,这些数据是对象的混合体,其中数据结构根据类型而变化,我不明白我应该如何为其创建一个好的类型结构。 我相信我做得对,但是当我尝试检查它是哪种类型时,我收到错误属性 X 在类型上不存在。

type EchoGroup = {
  group: {
    img: { name: string; type: "ECHO" };
    specialData: { meta: string; copy: string }[];
  };
  owner: boolean;
};
type CharlieGroup = {
  group: {
    img: { name: string; type: "CHARLIE" };
    specialData: { meta: string; usage: string; price: number }[];
  };
  owner: boolean;
};

const sample: (EchoGroup | CharlieGroup)[] = [
  {
    group: {
      img: { name: "Test 1", type: "CHARLIE" },
      specialData: [
        { meta: "string", usage: "private", price: 10 },
        { meta: "string", usage: "public", price: 20 },
      ],
    },
    owner: false,
  },
  {
    group: {
      img: { name: "Test 2", type: "ECHO" },
      specialData: [{ meta: "string", copy: "private" }],
    },
    owner: true,
  },
];
sample.map((single: EchoGroup | CharlieGroup) => {
  switch (single.group.img.type) {
    case "ECHO":
      console.log(single.group.specialData[0].copy);
      // Error:
      // Property 'copy' does not exist on type '{ meta: string; copy: string; }[] | { meta: string; usage: string; price: number; }[]'.
      // Property 'copy' does not exist on type '{ meta: string; copy: string; }[]'.
      break;
    case "CHARLIE":
      console.log(single.group.specialData[0].price);
      break;
  }
});

1 个答案:

答案 0 :(得分:1)

问题是 typescript 的类型缩小了父对象的 doesn't narrow type 取决于子对象的判别值:

type A = { type: "a", a: number }
type B = { type: "b", b: number }

type X = { type: A, a: string }
type Y = { type: B, b: string }

declare let x: X | Y

if (x.type.type === "a") {
    x.a // Type Error
    x.type.a // no error. `x.type` is narrowed to A
}

playground link

<块引用>

判别属性仅适用于它直接所属的对象。因此,在您的示例中,在 if 块内,您可以访问 x.type.a(但不能访问 x.type.b),但对包含对象 x 没有影响。

有一个 PR 来提供这种功能。但它仍在进行中。

截至目前,在任何结构中缩小类型的唯一方法是将判别属性保持在同一级别或更高级别:

type EchoGroup = {
  group: {
    type: 'ECHO',
    img: { name: string };
    specialData: { meta: string; copy: string }[];
  };
  owner: boolean;
};
type CharlieGroup = {
  group: {
    type: 'CHARLIE',
    img: { name: string };
    specialData: { meta: string; usage: string; price: number }[];
  };
  owner: boolean;
};
...
sample.map((single: EchoGroup | CharlieGroup) => {
  switch (single.group.type) {
    case "ECHO":
      console.log(single.group.specialData[0].copy); // works as expected
      break;
    case "CHARLIE":
      console.log(single.group.specialData[0].price); // works too
      break;
  }
});

playground link