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

查看:101
本文介绍了在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?

推荐答案

这实际上是 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>时,{...} 我的意思是,它适用于 all 所有可能的类型参数 T .这样, Foo< T> 消费者可以指定 T 的值并使用它们,而 provider Foo< T> 需要考虑所有可能性.

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指令< T>存在.{...} 表示存在类型参数 T .这意味着 Instruction provider 可以指定 T 的值,并使用消费者来做他们想要的事情. Instruction 的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;

SomeInstruction 是一个函数,该函数调用一个为任何 T 接受 Instruction< T> 的函数,并返回结果.请注意, SomeInstruction 如何本身不再不再依赖 T .您可能想知道如何获取一个 SomeInstruction ,但这也很简单.让我们做一个辅助函数,将任何 Instruction< T> 转换为 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 类型是什么的方式出现,因此必须处理尽可能 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 ,并使用类型断言来获取所需的类型.Blecch.

  • 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< T1> ;, Instruction< T2>;,指令< T3>] 类型.不过,这不适用于 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< T> 的任何内容,都必须独立于 T (例如,我可以编写 i.promise.then(i.回调),但我无能为力,对吗?),所以制作一个 Instruction 泛型函数,该函数需要一个有效的promise-and-callback对来创建,并返回非具有所需的任何功能的通用功能,仅此而已.在某种意义上,这是精简"的.存在性,不允许用户单独访问内部的Promise-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天全站免登陆