[英]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!看起来不错!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.