简体   繁体   English

从嵌套的 object 生成 TypeScript 类型

[英]Generating TypeScript types from a nested object

I'm trying to generate several types from an object, but have been completely stuck.我正在尝试从 object 生成几种类型,但已经完全卡住了。 Even the non-TS way (first generating more simple objects/arrays), and creating types from those, doesn't seem to work.即使是非 TS 方式(首先生成更简单的对象/数组)并从中创建类型,似乎也不起作用。 I'm trying to avoid repeating the information stored in the object.我试图避免重复存储在 object 中的信息。

I really appreciate any help!我真的很感激任何帮助!

// Object that I want to generate types from
const PRODUCT_SECTIONS = {
  fruit: {
    name: "Delicious fruit",
    products: {
      banana: "Banana",
      apple: "Apple",
    },
  },
  vegetables: {
    name: "Fresh vegetables",
    products: {
      mixedGreens: "Mixed greens",
      lettuce: "Lettuce",
      cucumbers: "Cucumbers",
    },
  },
  soda: {
    name: "Quality soda",
    products: {
      coke: "Coke",
      sprite: "Sprite",
    },
  },
} as const;

// Desired type 1: all section names
type SECTION_NAME = "Delicious fruit" | "Fresh vegetables" | "Quality soda";

// Desired type 2: all products
type PRODUCT = "Banana" | "Apple" | "Mixed greens" | "Lettuce" | "Cucumbers" | "Coke" | "Sprite";

// Desired type 3: product selection allowing 1 product per section
type SELECTED_PRODUCTS_STATE = {
  fruit: "Banana" | "Apple";
  vegetables: "Mixed greens" | "Lettuce" | "Cucumbers";
  soda: "Coke" | "Sprite";
};

I think your main stumbling block is that the operations you're performing are not distributing over union types .我认为您的主要绊脚石是您正在执行的操作没有分布union types上。 You want to get all the property value types of an object , which normally one could write as您想获取 object 的所有属性值类型,通常可以写为

type ValueOf<T> = T[keyof T];

But this often fails when T is a union of object types.但是当T是 object 类型的联合时,这通常会失败。 The problem is that if you have an object type like {a: string} and index into it with its key type "a" , you'll get its value type string .问题是,如果你有一个像{a: string}这样的 object 类型并用它的键类型"a" 索引到它,你会得到它的值类型string But if you have a union of object types like {a: string} | {b: number}但是如果你有一个 object 类型的联合体,比如{a: string} | {b: number} {a: string} | {b: number} , there's no key you can do to index into it safely. {a: string} | {b: number} ,您无法安全地索引它。 You can't index into it with "a" | "b"你不能用"a" | "b"来索引它"a" | "b" . "a" | "b" Since {a: string} has no known b property and {b: number} has no known a property, you can't index into the union with either key.由于{a: string}没有已知的b属性并且{b: number}没有已知a属性,因此您不能使用任一键索引到联合中。 And no, you don't get undefined if you index into {a: string} with a key that isn't a .不,如果您使用不是a的键对{a: string}进行索引,您不会得到undefined Object types in TypeScript are extendible and may have extra properties. TypeScript 中的 Object 类型是可扩展的,并且可能具有额外的属性。 If you index into {a: string} with "b" you would get the any type out, not undefined .如果你用"b"{a: string}进行索引,你会得到any类型,而不是undefined And the compiler complains anyway.无论如何编译器都会抱怨。 When you ask the compiler what keyof ({a: string} | {b: number}) is, it says it's the never type ;当你问编译器keyof ({a: string} | {b: number})是什么时,它说它never类型 there are no keys.没有钥匙。 And so ValueOf<{a: string} | {b: number}>所以ValueOf<{a: string} | {b: number}> ValueOf<{a: string} | {b: number}> is also never : ValueOf<{a: string} | {b: number}>never

type Sad = ValueOf<{ a: string } | { b: number }>
// type Sad = never

If you want to get string | number如果你想得到string | number string | number out of {a: string} | {b: number} {a: string} | {b: number}中的string | number | {a: string} | {b: number} , you conceptually want to break the union into pieces {a: string} and {b: number} , index into each with its known keys, and put them back together in a new union. {a: string} | {b: number} ,从概念上讲,您希望将联合拆分为{a: string}{b: number} ,使用其已知键对每个部分进行索引,然后将它们重新组合到一个新的联合中。 That is, you want to distribute the ValueOf<T> operation over unions in T .也就是说,您希望将ValueOf<T>操作分配T中的联合。

Luckily, TypeScript provides distributive conditional types with such behavior.幸运的是,TypeScript 提供了具有这种行为的分布式条件类型 If you have a generic type parameter T and check it against anything with a conditional type, like T extends unknown? F<T>: X如果你有一个泛型类型参数T并根据条件类型检查它,比如T extends unknown? F<T>: X T extends unknown? F<T>: X , the compiler will automatically distribute the F<T> operation over unions in T . T extends unknown? F<T>: X ,编译器将自动将F<T>操作分配给T中的联合。

So we can turn ValueOf into AllValuesOf like this:所以我们可以像这样把ValueOf变成AllValuesOf

type AllValuesOf<T> = T extends any ? T[keyof T] : never;

Yes, this looks like a no-op.是的,这看起来像是无操作。 But T extends any? T[keyof T]: never但是T extends any? T[keyof T]: never T extends any? T[keyof T]: never should be read "for each union member U in T , calculate U[keyof U] , and unite them back into a new union". T extends any? T[keyof T]: never应该被解读为“对于T中的每个联合成员U ,计算U[keyof U]并将它们重新组合成一个新的联合”。

Let's try it on our toy union type from above:让我们在上面的玩具联合类型上尝试一下:

type Happy = AllValuesOf<{ a: string } | { b: number }>
// type Happy = string | number

Works like a charm.奇迹般有效。


Armed with AllValuesOf<T> , you can do indexed accesses and mapped types more easily on your object type now:有了AllValuesOf<T> ,您现在可以在 object 类型上更轻松地进行索引访问和映射类型:

type ProductSections = typeof PRODUCT_SECTIONS;

type SECTION_NAME = AllValuesOf<ProductSections>["name"]
// type SECTION_NAME = "Delicious fruit" | "Fresh vegetables" | "Quality soda"

type PRODUCT = AllValuesOf<AllValuesOf<ProductSections>["products"]>
//"Banana" | "Apple" | "Mixed greens" | "Lettuce" | "Cucumbers" | "Coke" | "Sprite";

type SELECTED_PRODUCTS_STATE = { [K in keyof ProductSections]:
    AllValuesOf<ProductSections[K]["products"]> }
/* type SELECTED_PRODUCTS_STATE = {
    readonly fruit: "Banana" | "Apple";
    readonly vegetables: "Mixed greens" | "Lettuce" | "Cucumbers";
    readonly soda: "Coke" | "Sprite";
} */

Looks good!看起来不错!

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM