【问题标题】:Generating TypeScript types from a nested object从嵌套对象生成 TypeScript 类型
【发布时间】:2022-01-21 17:09:33
【问题描述】:

我正在尝试从一个对象生成几种类型,但完全被卡住了。即使是非 TS 方式(首先生成更简单的对象/数组)并从中创建类型,似乎也不起作用。我试图避免重复存储在对象中的信息。

非常感谢任何帮助!

// 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";
};

【问题讨论】:

  • this approach 适合您吗?如果是这样,我可以写一个答案;如果没有,请告诉我我缺少什么。
  • 该死的@jcalz 你是个巫师。我什至不知道AllValuesOf,我正在尝试使用typeof PRODUCT_SECTIONS[keyof typeof PRODUCT_SECTIONS]["products"]lmao
  • 谢谢@jcalz!你太棒了!这很好用。愚蠢的问题,但是当产品变成一个数组时,这个答案会如何改变呢?我很难修改你的答案以适应这个different object
  • 数组有数字索引以外的键,所以你需要像this 这样的东西。这似乎超出了这个问题的范围,对吧?我可以按要求回答原始问题吗?如果您有后续问题,您可以考虑发布一个新问题吗?这对你有用吗?
  • @Thomas 我正在写一个可以解释的答案,但请参阅the documentation for distributive conditional types

标签: javascript typescript object types tsc


【解决方案1】:

我认为你的主要绊脚石是你正在执行的操作没有分发union types。你想get all the property value types of an object,通常可以写成

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

但是当T 是对象类型的联合时,这通常会失败。问题是如果你有一个像{a: string}index into it这样的对象类型,它的键类型是"a",你会得到它的值类型string。但是如果你有一个对象类型的联合体,比如{a: string} | {b: number},那么你就无法安全地索引它。您无法使用"a" | "b" 对其进行索引。由于{a: string} 没有已知的b 属性并且{b: number} 没有已知的a 属性,因此您不能使用任一键索引到联合。不,如果你使用不是a 的键索引{a: string},你不会得到undefined。 TypeScript 中的对象类型是可扩展的,并且可能具有额外的属性。如果你用"b" 索引到{a: string},你会得到the any type,而不是undefined。无论如何编译器都会抱怨。当你问编译器keyof ({a: string} | {b: number}) 是什么时,它说它是the never type没有键。所以ValueOf&lt;{a: string} | {b: number}&gt; 也是never:?

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

如果你想从{a: string} | {b: number} 中得到string | number,你在概念上想把联合分解成{a: string}{b: number},用它的已知键索引每个,然后把它们重新组合成一个新的联盟。也就是说,您希望将ValueOf&lt;T&gt; 操作分发T 中的联合上。

幸运的是,TypeScript 为 distributive conditional types 提供了这种行为。如果你有一个泛型类型参数T 并检查它与任何带有条件类型的东西,比如T extends unknown ? F&lt;T&gt; : X ,编译器将自动将F&lt;T&gt; 操作分配给T 中的联合。

所以我们可以像这样将ValueOf 变成AllValuesOf

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

是的,这看起来像是无操作。但是T extends any ? T[keyof T] : never 应该读作“对于T 中的每个工会成员U,计算U[keyof U],并将它们重新联合成一个新的工会”。

让我们在上面的玩具联合类型上尝试一下:

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

像魅力一样工作。


有了AllValuesOf&lt;T&gt;,您现在可以更轻松地对您的对象类型进行索引访问和映射类型:

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";
} */

看起来不错!

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    • 2020-11-18
    • 2020-02-25
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多