在 TypeScript 中定义不同泛型类型的数组 [英] Defining an array of differing generic types in TypeScript
问题描述
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 的解析类型相同.
Given the above, I'd like TypeScript to recognise that the data parameter in the callback function is of the same type as the resolution of the Promise.
如果它是独立的,我可以这样做:
If it was stand alone, I could do:
interface Instruction<T> {
promise: Promise<T>,
callback?: ($html: JQuery, data: T) => void
}
但是我将如何定义数组,其中 T
可以在每一行表示不同的含义?
But then how would I define the array, where T
can mean something different on each line?
推荐答案
这实际上是 存在泛型,TypeScript 不直接支持它们(大多数具有泛型的语言也不直接支持它们,因此这不是 TypeScript 的一个特殊缺点).有一个开放的功能请求,microsoft/TypeScript#14466,要求这样做,但是它不是 TS4.1 语言的一部分.
This is practically the canonical use case for existential generic types, which are not directly supported in TypeScript (neither are they directly supported in most languages with generics, so it's not a particular shortcoming of TypeScript). There is an open feature request, microsoft/TypeScript#14466, asking for this, but it is not part of the language as of TS4.1.
TypeScript 中的泛型是通用的",这意味着当我说 class Foo<T>{...}
我的意思是它适用于 所有 可能的类型参数 T
.这让 Foo
的 consumer 指定 T
的值并用它做他们想做的事情,而 provider of Foo
需要考虑所有的可能性.
Generics in TypeScript are "universal", meaning that when I say class Foo<T> {...}
I mean that it works for all possible type parameters T
. That lets the consumer of a Foo<T>
specify the value for T
and do what they want with it, while the provider of Foo<T>
needs to allow for all possibilities.
像您试图描述的那样的异类集合需要存在的";仿制药.从某种意义上说,您希望 interface Instruction
表示 有 一个类型参数 T
对其有效.这意味着 Instruction
的 provider 可以指定 T
的值并用它做他们想做的事情,而 consumerInstruction
的 em> 需要考虑所有可能性.
Heterogeneous collections like the one you are trying to describe require "existential" generics. In some sense you want interface Instruction<exists T> {...}
to mean that there is a type parameter T
for which it works. Meaning that the provider of an Instruction
could specify the value for T
and do what they want with it, while the consumer of an Instruction
needs to allow for all possibilities.
有关通用与存在量化泛型的更多信息,请参阅此 SO 问答.
For more information about universally-vs-existentially quantified generics, see this SO question and answer.
虽然 TypeScript 没有直接支持,但有间接支持.普遍性和存在性之间的区别与谁在看类型有关.如果你转换生产者和消费者的角色,你就会得到类似存在主义的行为.这可以通过回调来完成.因此,可以在 TypeScript 中编码.
While there is no direct support for existentials in TypeScript, there is indirect support. The difference between a universal and an existential has to do with who is looking at the type. If you switch the role of producer and consumer, you get existential-like behavior. This can be accomplished via callbacks. So existentials can be encoded in TypeScript.
让我们看看如何为 Instruction
做到这一点.首先,让我们将 Instruction
定义为通用泛型,即独立"指令.您提到的版本(我正在删除此代码中的 JQuery
依赖项):
Let's look at how we might do it for Instruction
. First, let's define Instruction
as a universal generic, the "standalone" version you mentioned (and I'm removing the JQuery
dependency in this code):
interface Instruction<T> {
promise: Promise<T>,
callback?: (data: T) => void
}
这是存在编码,SomeInstruction
:
type SomeInstruction = <R>(cb: <T>(instruction: Instruction<T>) => R) => R;
A SomeInstruction
是一个函数,它调用一个函数,该函数接受任何 T
的 Instruction
并返回结果.请注意 SomeInstruction
本身如何不再依赖于 T
.您可能想知道如何获得SomeInstruction
,但这也相当简单.让我们创建一个辅助函数,将任何 Instruction
转换为 SomeInstruction
:
A SomeInstruction
is a function that calls a function that accepts an Instruction<T>
for any T
and returns the result. Notice how SomeInstruction
does not itself depend on T
anymore. You might wonder how to get a SomeInstruction
, but this is also fairly straightforward. Let's make a helper function that turns any Instruction<T>
into a SomeInstruction
:
const someInstruction = <T,>(i: Instruction<T>): SomeInstruction => cb => cb(i);
最后我们可以制作您的异构集合:
Finally we can make your hetereogeneous collection:
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)
})
]
根据需要进行所有类型检查.
That all type checks, as desired.
实际上使用一个SomeInstruction
比使用一个Instruction
要复杂一些,因为它需要一个回调.但这并不可怕,并且再次允许 T
类型参数以消费者不知道实际 T
类型是什么的方式出现,因此必须处理它作为任何可能的 T
:
Actually using a SomeInstruction
is a bit more involved than using an Instruction<T>
, since it takes a callback. But it's not terrible, and again, allows the T
type parameter to appear in a way that the consumer doesn't know what the actual T
type is and therefore has to treat it as any possible 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
}))
不错.
还有其他解决方法,但您真正想要的是存在主义.为完整起见,以下是我将提及但未实施的一些可能的解决方法:
There are other workarounds, but an existential is what you really want. For completeness, here are some possible workarounds that I will mention but not implement:
放弃类型安全并使用
any
或unknown
并使用类型断言来获取您想要的类型.布莱克.
give up on type safety and use
any
orunknown
and use type assertions to get back the types you want. Blecch.
使用 映射元组类型,您可以在其中将 [T1, T2, T3]
之类的类型转换为相应的 [Instruction
类型.但是,这不适用于 push()
,因此您也需要以某种方式解决这个问题.
Use a mapped tuple type where you convert a type like [T1, T2, T3]
to the corresponding [Instruction<T1>, Instruction<T2>, Instruction<T3>]
type. This won't work with push()
though, so you'd need to work around that somehow too.
重构 Instruction
以免需要/暴露泛型.无论消费者计划用Instruction
做什么,它都必须独立于T
(例如,我可以写i.promise.then(i.callback)
但我不能做很多其他的事情,对吧?),所以创建一个 Instruction
通用函数,它需要一个有效的 promise-and-callback 对来创建,并返回一些非-通用,具有您需要的任何功能,仅此而已.从某种意义上说,这是精简"的.不允许消费者单独访问内部承诺回调对的存在.
Refactor Instruction
so as not to need/expose generics. Whatever the consumer plans to do with an Instruction<T>
it has to be independent of T
(e.g., I can write i.promise.then(i.callback)
but I can't do much else, right?), so make an Instruction
generic function which requires a valid promise-and-callback pair to create, and returns something non-generic with whatever functionality you need and that's it. In some sense this is a "stripped-down" existential that does not allow the consumer to access the internal promise-callback pair separately.
这篇关于在 TypeScript 中定义不同泛型类型的数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!