typescript 声明一个字段的类型基于另一个字段的值

k97glaaz  于 2023-05-08  发布在  TypeScript
关注(0)|答案(1)|浏览(244)

我想像这样声明类型:

type ElementCreatorParameters = {
  tagName: keyof HTMLElementTagNameMap
  children?: Array<ElementCreatorParameters>
  onCreate?(element: HTMLElementTagNameMap[tagName]): void
}

第一个问题是,我不能使用字段tagName的值来定义element的类型(如果我是正确的,那是因为类型必须是静态的)
第二个问题是,当我开始使用泛型类型时,我需要将此泛型类型传递到children字段中:

type ElementCreatorParameters<T extends keyof HTMLElementTagNameMap> = {
  tagName: T
  children?: Array<ElementCreatorParameters<T>>
  onCreate?(element: HTMLElementTagNameMap[T]): void
}

children与其父代的泛型类型不同
我希望所有这些都能像这样定义对象:

{
  tagName: 'form',
  onCreate: (element) => {
    // Here the element must be HTMLFormElement 
    element
  },
  children: [
    {
      tagName: 'input',
      onCreate: (element) => {
        // Here the element must be HTMLInputElement
        element
      },
      children: [
        {
          tagName: 'a',
          onCreate: (element) => {
            // Here the element must be HTMLAnchorElement
            element
          },
        },
      ]
    },
  ],
}
wmvff8tz

wmvff8tz1#

您可以将ElementCreatorParameters表示为联合类型,其中联合中的每个成员对应于keyof HTMLElementTagNameMap中的tagName的一个选择。当然,那个联盟里会有100多个成员,所以手工写出来会很烦人……但这是一个有限的数字,所以联合至少是一个可能的解决方案。幸运的是,我们不需要把工会成员一个个写出来;我们可以让编译器为我们计算它。就像这样:

type ElementCreatorParameters = { [K in keyof HTMLElementTagNameMap]: {
  tagName: K
  children?: Array<ElementCreatorParameters>
  onCreate?(element: HTMLElementTagNameMap[K]): void
} }[keyof HTMLElementTagNameMap]

一般的方法是先生成一个mapped type,然后再生成index into it及其所有键,以获得Map类型的属性的并集。这可以称为microsoft/TypeScript#47109中创造的 * 分布式对象类型 *。如果你有一组键type KK = K1 | K2 | K3 | ⋯,那么{[K in KK]: F<K>}[KK]将计算为F<K1> | F<K2> | F<K3> | ⋯
在上面的例子中,KKkeyof HTMLElementTagNameMapF<K>

{
  tagName: K
  children?: Array<ElementCreatorParameters>
  onCreate?(element: HTMLElementTagNameMap[K]): void
}

所以ElementCreatorParameters是一个大的联合,如果你在支持IntelliSense的IDE中悬停quickinfo,你可以看到它:

/* type ElementCreatorParameters = {
    tagName: "object";
    children?: ElementCreatorParameters[] | undefined;
    onCreate?(element: HTMLObjectElement): void;
} | {
    tagName: "a";
    children?: ElementCreatorParameters[] | undefined;
    onCreate?(element: HTMLAnchorElement): void;
} | ... 108 more ... | {
    ...;
} */

让我们测试一下:

const e: ElementCreatorParameters = {
  tagName: 'form',
  onCreate: (element) => {
    element // (parameter) element: HTMLFormElement
  },
  children: [
    {
      tagName: 'input',
      onCreate: (element) => {
        element // (parameter) element: HTMLInputElement
      },
      children: [
        {
          tagName: 'a',
          onCreate: (element) => {
            element // (parameter) element: HTMLAnchorElement
          },
        },
      ]
    },
  ],
}

看起来不错!所有的onCreate参数都是根据ElementCreatorParameters联合体的相应成员上下文推断出来的。
Playground链接到代码

相关问题