typescript - 能否根据泛型参数将泛型 TypeScript 类型映射到另一种类型?

标签 typescript typescript-generics

我有一系列 API,其中包含各种相关的泛型类型(以及关联的泛型类型约束)。我合成了一个示例并将其粘贴在下面,使用众所周知的类型来模拟一些构造,仅供说明之用。

在使用这些类型时,我经常想从另一个类型创建一个类型,重用引用类型的泛型类型参数。该操作类似于接口(interface)可以根据提供给其定义的泛型类型来约束其属性类型的方式(您将在代码示例中看到,这就是我正在做的解决方法)。

type Properties = { [name: string]: string }

interface SomeGeneric<A,B extends Properties,C extends HTMLElement> {
    someConcreteMethod: () => A
    someOtherConcreateMethod: (b: B) => void
    aConcreteProperty: C

    //  Fake properties, don't really exist at runtime, just to allow type derivation
    //  This is obviously terrible. Is there anything better?
    relatedInterface?: RelatedInterface<A,B>
    anotherRelatedInterface?: AnotherRelatedInterface<A,C>
    thirdRelatedInterface?: ThirdRelatedInterface<A,B,C>
}

interface RelatedInterface<A,B extends Properties> {
    yetAnotherMethod: (a: A, b: B) => void
}

interface AnotherRelatedInterface<A,C extends HTMLElement> {
    oneMoreMethod: (c: C) => A
}

interface ThirdRelatedInterface<A,B extends Properties,C extends HTMLElement> {
    thirdMethod: (a: A, b: B, c: C) => () => void

    //  Fake properties again
    relatedInterface?: RelatedInterface<A,B>
    anotherRelatedInterface?: AnotherRelatedInterface<A,C>
}

type ConcreteB = { name: string };

interface SomeConcreteType extends SomeGeneric<string,ConcreteB,HTMLDivElement> {}

//  So I have a family of related types, all generic, pertaining to one another. In this example, I've simplified it,
//  by making one type the canonical type (SomeGeneric). In my domain, that's not really true (you can see below I
//. create a type based on a ThirdRelatedInterface type also), although I could obviously pick one if I had to do that.
//. I've considered trying to base everything on SomeGeneric (and had the other types receive a type parameter that
//. extends SomeGeneric<A,B,C> rather than the type parameters A, B, and C themselves) but because these types all
//. have standalone use cases it would force me to declare useless unneeded type parameters when creating the
//. standalone types.

interface SomeConcreteRelatedInterfaceVerbosely extends RelatedInterface<string,ConcreteB> {}
interface AnotherConcreteRelatedInterfaceVerbosely extends AnotherRelatedInterface<string,HTMLDivElement> {}
interface ThirdConcreteRelatedInterfaceVerbosely extends ThirdRelatedInterface<string,ConcreteB,HTMLDivElement> {}

//  This is the only way I've been able to find to derive the related types without repeating the generic type parameters.
//. Because I have a family of related types, they all have synthetic, fake, type-inference-only properties like these.
//  Is there a better way?

type SomeConcreteRelatedInterfaceDeclaratively = SomeConcreteType["relatedInterface"]
type AnotherConcreteRelatedTypeDeclaratively = SomeConcreteType["anotherRelatedInterface"]
type ThirdConcreteTypeDeclaratively = SomeConcreteType["thirdRelatedInterface"]

type DerivedFromThirdTypeDeclaratively = ThirdConcreteTypeDeclaratively["anotherRelatedInterface"]

由于多种原因,这显然是可怕的,主要原因是现在编译器认为这些对象具有这些类型的属性,而实际上我只是使用属性“定义”来进行从一种受约束的泛型类型到另一种受约束的泛型类型的转换,我不知道该怎么做。

这似乎与“映射类型”不对应 feature ,这与迭代某种类型的键有关。我在 generics 中也找不到任何内容讨论如何使用类型约束的文档。 (请参阅下面关于一个 SO 问题,该问题使用 infer 从泛型类型中提取类型参数。)

之前的相关问题

有几个问题可以解决 TypeScript 的情况,即有人想要通过约束使一种泛型类型依赖于另一种泛型类型(例如,参见 TypeScript Generic Parameter Type Extending Another Type ),但我还没有成功找到一个处理泛型类型的问题不同类型的相互依赖。

我发现至少直接操作泛型类型参数的一件事是 Typescript: Retrieve element type information from array type ,它说明了如何将 Array 类型转换为其元素的类型(这是通用参数):

        export type ArrayElement<ArrayType extends readonly unknown[]> =
            ArrayType extends readonly (infer ElementType)[] ? ElementType : never;

所以这肯定更类似于我想要做的事情,尽管我想以某种方式将提取的类型进一步转换为我的相关类型之一。我必须承认,我还没有弄清楚 infer 是如何工作的,所以我不太明白如何使用它,或者它是否适用。

This question关于具有基于泛型类型的泛型约束的类型(以及使用泛型类型参数的参数的愿望)也得到了一些稍微类似的东西,但仍然属于单一类型。不过,该解决方案的某些内容可能适用。

最佳答案

不幸的是,目前,在不知道类型本身的情况下,无法获取类型/接口(interface)的泛型参数。因此这个问题不会有完美的解决方案。

一种选择是为与其他接口(interface)相关的每种接口(interface)提供一个泛型类型。我们必须使用infer keyword为了达成这个。您可以解读infer作为变量声明。

文档示例:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

简单来说,infer声明一个类似于占位符的参数,它将评估条件的左侧和右侧:

Array<string> extends Array<infer R> , R必须是string对于方程。我们还可以对推断的参数写一些约束:

infer R extends string ,那么编译器将处理 R作为string不是未知参数。

我们将以相同的方式使用它,但使用更多的参数,这根本不会改变逻辑。假设T延伸SomeGeneric<any, any, any> :

 T extends SomeGeneric<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >

这样,我们就得到了T的所有三个参数。具有正确的约束( stringPropertiesHTMLElement )。然后我们可以将它们的参数传递给适当的接口(interface)。

SomeGeneric 的 setter/getter :

type GetSomeGenericRelatedInterfaces<T extends SomeGeneric<any, any, any>> =
  T extends SomeGeneric<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >
    ? {
        relatedInterface?: RelatedInterface<A, B>;
        anotherRelatedInterface?: AnotherRelatedInterface<A, C>;
        thirdRelatedInterface: ThirdRelatedInterface<A, B, C>;
      }
    : never;

ThirdRelatedInterface 的 setter/getter :

type GetThirdRelatedInterfaces<T extends ThirdRelatedInterface<any, any, any>> =
  T extends ThirdRelatedInterface<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >
    ? {
        relatedInterface?: RelatedInterface<A, B>;
        anotherRelatedInterface?: AnotherRelatedInterface<A, C>;
      }
    : never;

通用包装:

type GetRelatedInterfaces<T> = T extends SomeGeneric<any, any, any>
  ? GetSomeGenericRelatedInterfaces<T>
  : T extends ThirdRelatedInterface<any, any, any>
  ? GetThirdRelatedInterfaces<T>
  : never;

用法:

type ConcreteB = { name: string };

interface SomeConcreteType
  extends SomeGeneric<string, ConcreteB, HTMLDivElement> {}

interface ThirdConcreteRelatedInterfaceVerbosely
  extends ThirdRelatedInterface<string, ConcreteB, HTMLDivElement> {}


// type Case1 = {
//   relatedInterface?: RelatedInterface<string, ConcreteB>;
//   anotherRelatedInterface?: AnotherRelatedInterface<string, HTMLDivElement>;
//   thirdRelatedInterface: ThirdRelatedInterface<
//     string,
//     ConcreteB,
//     HTMLDivElement
//   >;
// };
type Case1 = GetRelatedInterfaces<SomeConcreteType>;

// type Case2 = {
//   relatedInterface?: RelatedInterface<string, ConcreteB>;
//   anotherRelatedInterface?: AnotherRelatedInterface<string, HTMLDivElement>;
// }
type Case2 = GetRelatedInterfaces<ThirdConcreteRelatedInterfaceVerbosely>;

playground

关于typescript - 能否根据泛型参数将泛型 TypeScript 类型映射到另一种类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76258091/

相关文章:

angular - HostBinding 到匿名 lambda 函数?

javascript - Angular2 等待 DOM 元素加载

javascript - 如何检测到在 &lt;input type ="file"/> 选择文件对话框中单击了取消按钮?

javascript - 为什么我不能将 typescript 泛型类型分配给该类型的联合?

typescript - 如何将元组类型转换为联合?

typescript - 推断列数组中 rowData 和 cellData 的类型

typescript - 如何正确设置 NestJS 中的配置?

Typescript:使用函数数组的 ReturnType(已编辑)

typescript - 如何从父类(super class)方法而不是父类(super class)类型返回子类型

javascript - 将子类别作为数组对象中的数组