鉴于以下结构:
const myForm = {
widget: "col",
children: [
{
widget: 'row',
children: [
{
widget: 'text',
prop: 'name'
},
{
widget: 'number',
prop: 'age'
}
]
},
{
widget: 'text',
prop: 'location',
optional: true
}
]
} as const
type MyForm = Infer<typeof myForm>我想推断出以下类型:
{
name: string
age: number
location?: string
}到目前为止,我成功地“遍历”了对象树,并推断了每个小部件的类型,但是我仍然无法构建最终的对象。这是我的游乐场链接。如果您悬停在MyForm类型上,您将看到我迄今所取得的成就。
发布于 2022-08-07 12:18:59
让我们从简单的事情开始。在某个时候,我们需要从字符串表示"text"映射到string类型和其他类型。在您的操场上,您使用了条件类型。这是可行的,但是使用一个简单的映射可以更容易地理解和扩展(而且性能也更好^^)。
type WidgetToType = {
"text": string
"number": number
}我还为给定的数据结构定义了一个类型的FormElement。
type FormElement = {
widget: string,
children?: readonly FormElement[],
prop?: string,
optional?: boolean
}这将使以后通过索引泛型类型T来更容易地访问属性。我们可以使用类型作为T的约束来执行类似于T["prop"]的事情,而不需要检查属性是否存在。
现在说到更难的部分。我们知道结果类型是一个平面对象。
type MyForm = {
name: string;
age: number;
location?: string | undefined;
}我们还知道,我们可以使用映射类型构造这样的对象类型,并且我们需要一个联合来进行映射。因此,理想情况下,我们可以创建一个助手类型,它将从给定类型创建一个联合,如下所示:
{
widget: "text";
prop: "name";
} | {
widget: "number";
prop: "age";
} | {
widget: "text";
prop: "location";
optional: true;
}此联合应包含关于每个结果属性的所有所需信息。
为了创建联合,我编写了递归类型GetPropsDeeply。
type GetPropsDeeply<T extends FormElement> =
| (T["prop"] extends string ? T : never)
| (T["children"] extends readonly any[]
? T["children"][number] extends infer U
? U extends FormElement
? GetPropsDeeply<U>
: never
: never
: never
)GetPropsDeeply从对象类型的根开始,并通过执行两次检查开始构建联合。首先,它检查是否设置了T["prop"]。如果是这样,我们可以将T添加到联合中。然后检查是否设置了T["children"]。如果是这样的话,我们通过用children索引T["children"]将T["children"]元组转换为子元素的联合。我们现在可以在对GetPropsDeeply的递归调用中使用这些联合元素。
这里还有一个“隐藏”的TypeScript技工。通过将T["children"][number]存储在U中并检查是否存在U extends FormElement,我们是GetPropsDeeply上的联合元素。
因此,我们没有使用GetPropsDeeply<E_1 | E_2 | E_3> (其中E_N是一个联合元素),而是在GetPropsDeeply上分发,从而生成了GetPropsDeeply<E_1> | GetPropsDeeply<E_2> | GetPropsDeeply<E_3>。
现在到最后一部分。
type Infer<T extends FormElement> = (GetPropsDeeply<T> extends infer U ? ({
[K in U as K extends { optional: true }
? never
: K["prop" & keyof K] & string
]: WidgetToType[K["widget" & keyof K] & keyof WidgetToType]
} & {
[K in U as K extends { optional: true }
? K["prop" & keyof K] & string
: never
]?: WidgetToType[K["widget" & keyof K] & keyof WidgetToType]
}) : never) extends infer O ? { [K in keyof O]: O[K] } : never我们调用GetPropsDeeply<T>并将结果存储在U中。因为optional是true的属性应该是可选的,所以我们需要构造两个独立的映射类型,其中一个使用?表示法并将它们相交。对于各自的映射类型,我们筛选出联合元素,其中optional分别是true或false。为了获得属性名,我们执行K["prop"],并在这里使用映射获得相应类型的K["widget"]。
https://stackoverflow.com/questions/73267075
复制相似问题