在 TypeScript 中定义不同泛型类型的数组

Posted

技术标签:

【中文标题】在 TypeScript 中定义不同泛型类型的数组【英文标题】:Defining an array of differing generic types in TypeScript 【发布时间】:2021-03-15 15:37:45 【问题描述】:
interface Instruction 
  promise: Promise<unknown>,
  callback?: ($html: JQuery, data: unknown ) => void


const arr: Instruction[] = [
   promise: Promise.resolve( foo: 'bar' ), callback: ($html, data) => console.log(data.foo) ,
   promise: Promise.resolve( bar: 'foo' ), callback: ($html, data) => console.log(data.bar) 
];

鉴于上述情况,我希望 TypeScript 能够识别回调函数中的 data 参数与 Promise 的解析类型相同。

如果它是独立的,我可以这样做:

interface Instruction<T> 
  promise: Promise<T>,
  callback?: ($html: JQuery, data: T) => void

但是我将如何定义数组,其中T 在每一行可能意味着不同的东西?

【问题讨论】:

在您的实际代码中,arr 会是一个长度和类型在编译时已知的不可变数组吗? @Aplet123,在实际代码中,数组是在更大的类中构造的,使用push 【参考方案1】:

也许对你有帮助:

type JQuery = 'jquery'

interface Instruction<T> 
  promise: Promise<T>,
  callback?: ($html: JQuery, data: T) => void


const builder = <T,>(arg: T): Instruction<T> => ( promise: Promise.resolve(arg), callback: ($html, data) => data )

const arr = [
  builder( foo: 'bar' ),
  builder( bar: 'foo' ),
];

如果这个数组是可变的,事情就会变得复杂。

更新

您必须添加额外的逗号&lt;T,&gt;,因为没有, TS 认为您正在尝试使用某种react jsx 语法。 你应该只用箭头函数来使用这个技巧。它与function 一样工作。 代替extra comma,您可以使用&lt;T extends unknown&gt;&lt;T extends &gt;

【讨论】:

谢谢@capain-yossarian!我没有想到要像这样包装指令。你能解释一下&lt;T,&gt; 发生了什么吗?我搜索了互联网,但无法理解。这是某种捷径吗?【参考方案2】:

这实际上是 existential generic types 的规范用例,TypeScript 不直接支持它们(大多数具有泛型的语言也不直接支持它们,因此这不是 TypeScript 的特殊缺点)。有一个开放的功能请求,microsoft/TypeScript#14466,要求这样做,但它不是 TS4.1 语言的一部分。

TypeScript 中的泛型是“通用的”,这意味着当我说class Foo&lt;T&gt; ... 时,我的意思是它适用于所有 可能的类型参数T。这让Foo&lt;T&gt;消费者 指定T 的值并用它做他们想做的事,而Foo&lt;T&gt;提供者 需要允许所有可能性。

像您试图描述的那样的异构集合需要“存在”泛型。从某种意义上说,您希望interface Instruction&lt;exists T&gt; ... 意味着一个类型参数T 可以工作。这意味着Instructionprovider 可以指定T 的值并用它做他们想做的事情,而Instructionconsumer 需要允许所有的可能性。

有关普遍与存在量化的泛型的更多信息,请参阅this SO question and answer。


虽然 TypeScript 中没有 direct 支持存在,但有 indirect 支持。普遍和存在之间的区别与谁在看类型有关。如果你转换生产者和消费者的角色,你就会得到类似存在主义的行为。这可以通过回调来完成。所以存在词可以在 TypeScript 中编码

让我们看看如何为Instruction 做这件事。首先,让我们将Instruction 定义为通用泛型,即您提到的“独立”版本(我将删除此代码中的JQuery 依赖项):

interface Instruction<T> 
    promise: Promise<T>,
    callback?: (data: T) => void

这是存在编码,SomeInstruction

type SomeInstruction = <R>(cb: <T>(instruction: Instruction<T>) => R) => R;

SomeInstruction 是一个函数,它调用一个函数,该函数接受任何 TInstruction&lt;T&gt; 并返回结果。请注意SomeInstruction 本身如何不再依赖于T。您可能想知道如何获得 SomeInstruction,但这也相当简单。让我们创建一个辅助函数,将任何Instruction&lt;T&gt; 转换为SomeInstruction

const someInstruction = <T,>(i: Instruction<T>): SomeInstruction => cb => cb(i);

最后我们可以制作你的异构集合:

const arr: SomeInstruction[] = [
    someInstruction( 
      promise: Promise.resolve( foo: 'bar' ), 
      callback: (data) => console.log(data.foo) 
    ),
    someInstruction( 
      promise: Promise.resolve( bar: 'foo' ), 
      callback: (data) => console.log(data.bar) 
    )
]

根据需要进行所有类型检查。


实际上使用SomeInstruction 比使用Instruction&lt;T&gt; 更复杂一些,因为它需要一个回调。但这并不可怕,并且再次允许T 类型参数以消费者不知道实际T 类型是什么的方式出现,因此必须将其视为任何可能的T

// writing out T for explicitness here
arr.forEach(someInstruction => someInstruction(<T,>(i: Instruction<T>) =>     
    i.promise.then(i.callback); // works
))

// but it is not necessary:
arr.forEach(someInstruction => someInstruction(i =>     
    i.promise.then(i.callback); // works
))

不错。


还有其他解决方法,但存在主义才是你真正想要的。为了完整起见,以下是一些可能的解决方法,我将提及但未实施:

放弃类型安全并使用anyunknown 并使用类型断言来取回您想要的类型。布莱克。

使用mapped tuple type 将[T1, T2, T3] 之类的类型转换为相应的[Instruction&lt;T1&gt;, Instruction&lt;T2&gt;, Instruction&lt;T3&gt;] 类型。不过,这不适用于 push(),因此您也需要以某种方式解决这个问题。

重构 Instruction 以免需要/公开泛型。无论消费者打算用Instruction&lt;T&gt; 做什么,它都必须独立于T(例如,我可以写i.promise.then(i.callback),但我不能做很多其他事情,对吧?),所以创建一个Instruction 通用函数这需要一个有效的 promise-and-callback 对来创建,并返回具有您需要的任何功能的非泛型的东西,仅此而已。从某种意义上说,这是一个“精简”的存在,它不允许消费者单独访问内部的 promise-callback 对。


Playground link to code

【讨论】:

类型系统聂文章! @jcalz:这太棒了,谢谢。我想我明白这里发生了什么,但我正在努力使用这种语法&lt;T,&gt;。为什么是逗号?它似乎声明了一个未使用的类型参数,但我不明白为什么。 尾随逗号不引入新参数。这只是为了阻止编译器在您使用 tsx/jsx 的情况下生气。 (在 tsx/jsx 模式下,编译器将该位置的 &lt;T&gt; 视为标记并抱怨)。如果为您编译,您可以删除逗号。 哇哦,好吧!我无法决定我现在是否感到或多或少愚蠢。谢谢!

以上是关于在 TypeScript 中定义不同泛型类型的数组的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript-泛型

Typescript #6 泛型

TypeScript:TypedArray的泛型类型定义

TypeScript泛型

TypeScript - 如何将索引签名表示为泛型类型

TypeScript类型断言数组的类型函数的类型