在 TypeScript 中定义不同泛型类型的数组 [英] Defining an array of differing generic types in TypeScript

查看:33
本文介绍了在 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.这让 Fooconsumer 指定 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 对其有效.这意味着 Instructionprovider 可以指定 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 是一个函数,它调用一个函数,该函数接受任何 TInstruction 并返回结果.请注意 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:

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

  • give up on type safety and use any or unknown and use type assertions to get back the types you want. Blecch.

使用 映射元组类型,您可以在其中将 [T1, T2, T3] 之类的类型转换为相应的 [Instruction, 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆