首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何从嵌套的对象/组件树中推断结果对象类型

如何从嵌套的对象/组件树中推断结果对象类型
EN

Stack Overflow用户
提问于 2022-08-07 11:37:08
回答 1查看 49关注 0票数 1

鉴于以下结构:

代码语言:javascript
复制
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>

我想推断出以下类型:

代码语言:javascript
复制
{
  name: string
  age: number
  location?: string
}

到目前为止,我成功地“遍历”了对象树,并推断了每个小部件的类型,但是我仍然无法构建最终的对象。这是我的游乐场链接。如果您悬停在MyForm类型上,您将看到我迄今所取得的成就。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-08-07 12:18:59

让我们从简单的事情开始。在某个时候,我们需要从字符串表示"text"映射到string类型和其他类型。在您的操场上,您使用了条件类型。这是可行的,但是使用一个简单的映射可以更容易地理解和扩展(而且性能也更好^^)。

代码语言:javascript
复制
type WidgetToType = {
  "text": string
  "number": number
}

我还为给定的数据结构定义了一个类型的FormElement

代码语言:javascript
复制
type FormElement = { 
  widget: string, 
  children?: readonly FormElement[], 
  prop?: string, 
  optional?: boolean 
}

这将使以后通过索引泛型类型T来更容易地访问属性。我们可以使用类型作为T的约束来执行类似于T["prop"]的事情,而不需要检查属性是否存在。

现在说到更难的部分。我们知道结果类型是一个平面对象。

代码语言:javascript
复制
type MyForm = {
    name: string;
    age: number;
    location?: string | undefined;
}

我们还知道,我们可以使用映射类型构造这样的对象类型,并且我们需要一个联合来进行映射。因此,理想情况下,我们可以创建一个助手类型,它将从给定类型创建一个联合,如下所示:

代码语言:javascript
复制
{
    widget: "text";
    prop: "name";
} | {
    widget: "number";
    prop: "age";
} | {
    widget: "text";
    prop: "location";
    optional: true;
}

此联合应包含关于每个结果属性的所有所需信息。

为了创建联合,我编写了递归类型GetPropsDeeply

代码语言:javascript
复制
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>

现在到最后一部分。

代码语言:javascript
复制
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中。因为optionaltrue的属性应该是可选的,所以我们需要构造两个独立的映射类型,其中一个使用?表示法并将它们相交。对于各自的映射类型,我们筛选出联合元素,其中optional分别是truefalse。为了获得属性名,我们执行K["prop"],并在这里使用映射获得相应类型的K["widget"]

游乐场

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73267075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档