根据打字稿中的对象结构

时间:2020-09-02 15:16:26

标签: typescript

我想让Typescript根据对象的形状推断对象的类型。 假设我们具有以下类型定义:

type Base = {
  id: string;
  attributes: any;
}

type ShapeOne = Base & {
  id: "one";
  attributes: {
    amount: 1 | 2 | 3;
  }
}

type ShapeTwo = Base & {
  id: "two";
  attributes: {
    size: "S" | "M" | "L";
  }
}

还有一个与ShapeOne相匹配的对象:

const shape = {
  id: "one",
  attributes: {
    amount: 1,
  }
}

现在我希望Typescript可以根据对象的形状提取/推断正确的类型:

type t = ShapeType<typeof shape>; // ShapeOne

通过阅读文档,嵌套条件类型应该可以实现这一点,但是不幸的是,我不知道对此有何进一步的了解。

1 个答案:

答案 0 :(得分:1)

按照惯例,您应该对此类对象结构使用interface而不是type别名:

interface Base {
  id: string;
  attributes: any;
}

interface ShapeOne extends Base {
  id: "one";
  attributes: {
    amount: 1 | 2 | 3;
  }
}

interface ShapeTwo extends Base {
  id: "two";
  attributes: {
    size: "S" | "M" | "L";
  }
}

这样做不是必需的,但是,如果不这样做,最终结果可能会显示为ShapeOne的定义,而不会在任何地方显示名称“ ShapeOne” 。同样,由于(例如){id: "two"; attributes: { size: "S" | "M" | "L"; }}已经是Base的子类型,因此没有真正的理由使用交集。只会使事情变得混乱。


无论如何,您都会想出一种实际的discriminated union类型,因为它在这里很有用:

type Shape = ShapeOne | ShapeTwo;

此后,我们需要更改定义shape的方式。在您的情况下,编译器会将其类型推断为

/* const shape: {
    id: string;
    attributes: {
        amount: number;
    };
} */

碰巧,这样的类型将被视为可分配给ShapeOne,但是有些偶然。编译器忘记了shape.idliteral string type "one"。它一直扩展到string。如果您有一个看起来像ShapeThree的{​​{1}},则编译器会认为{id: "three"; attributes: {amount: 4 | 5 | 6;}}可分配给shapeShapeOne

因此,您需要缩小ShapeThree的范围。您可以根据需要将其注释为shape

Shape

编译器会自动将const shape: Shape = { id: "one", attributes: { amount: 1, } } 的外观类型缩小control flow analysisshape

ShapeOne

type t0 = typeof shape; // ShapeOne 完全不需要。


或者,如果您不想将ShapeType<>注释为联合,则仍需要使用更窄的类型,可能通过const assertion

shape

请注意,const shapeNarrow = { id: "one", attributes: { amount: 1, } } as const; /* const shapeNarrow: { readonly id: "one"; readonly attributes: { readonly amount: 1; }; } */ 的类型现在是shapeNarrow子类型:更具体。然后,您终于可以编写嵌套的distributive conditional type来拔出工会的合适成员:

ShapeOne

所以您可以这样写type ExtractSubtype<T, U> = T extends any ? U extends T ? T : never : never; type t = ExtractSubtype<Shape, typeof shapeNarrow>; // type t = ShapeOne

ShapeType<>

是的!

请注意,分布式条件类型仅适用于裸类型参数,因此您不能重构为:

type ShapeType<U> = ExtractSubtype<Shape, U>;
type tGood = ShapeType<typeof shapeNarrow>; // ShapeOne ☺

Playground link to code

相关问题