[英]TypeScript type annotation to express variable numbers of generics?
I've been converting a project of mine from JavaScript to TypeScript, but this type annotation has me stumped. 我一直在将我的项目从JavaScript转换为TypeScript,但是这种类型注释使我感到困惑。 I have a
Serializer
interface and a class for combining these interfaces like the following: 我有一个
Serializer
接口和一个用于组合这些接口的类,如下所示:
interface Serializer<T> {
serialize(value: T): ArrayBuffer
}
class UnionSerializer {
constructor(types) {
this.types = types
}
serialize(value) {
for (const type of this.types) {
try {
return type.serialize(value) //throws error if type can't serialize value
}
catch (e) {}
}
throw new Error('No type matched')
}
}
How would I go about writing a type declaration for UnionSerializer
based on Serializer
? 如何基于
Serializer
为UnionSerializer
编写类型声明? Clearly its type depends on the types
array passed into it. 显然,其类型取决于传递给它的
types
数组。 So it has an interface like: 因此它具有如下接口:
interface UnionSerializerConstructor {
new <T1>([t1]: [Serializer<T1>]): Serializer<T1>
new <T1, T2>([t1, t2]: [Serializer<T1>, Serializer<T2>]): Serializer<T1 | T2>
//...
}
What is the most elegant way to write type annotations for the UnionSerializer
class, ideally without writing out all of the constructor annotations for different lengths of the types
array? 为
UnionSerializer
类编写类型注释的最优雅的方法是什么,理想情况下,不写出所有不同types
数组长度的构造函数注释?
Is there a difference between the type of new UnionSerializer([a,b])
and the type of new UnionSerializer([b,a])
? new UnionSerializer([a,b])
的类型与new UnionSerializer([b,a])
的类型之间有区别吗? It doesn't really look like it, given that your type signatures for UnionSerializerConstructor
return a Serializer<A | B>
鉴于您对
UnionSerializerConstructor
的类型签名返回了Serializer<A | B>
,因此它看起来并不是真的Serializer<A | B>
Serializer<A | B>
which would be the same as Serializer<B | A>
Serializer<A | B>
与Serializer<B | A>
相同 Serializer<B | A>
. Serializer<B | A>
。 In that case, good news! 在这种情况下,好消息! You don't need to worry about specifying an generic arbitrary-length tuple type, which you can't do yet .
您无需担心指定通用的任意长度元组类型,而这是您尚无法做到的 。 Instead, just accept an array:
而是接受一个数组:
interface UnionSerializerConstructor {
new <T>(types: T[]): Serializer<T>
}
If you pass in [a,b]
(where a
is an A
and b
is a B
) to the constructor, it will infer A | B
如果将
[a,b]
(其中a
是A
, b
是B
)传递给构造函数,它将推断A | B
A | B
for T
, as you want. A | B
代表T
,如您所愿。
Now, it looks like you expect the values passed into the constructor to themselves be Serializer
instances. 现在,您似乎希望传递给构造函数的值本身就是
Serializer
实例。 In that case, you should probably do this instead: 在这种情况下,您可能应该这样做:
interface UnionSerializerConstructor {
new <T>(types: (Serializer<T>)[]): Serializer<T>
}
I might as well go ahead and type your UnionSerializer
for you: 我不妨继续为您键入您的
UnionSerializer
:
class UnionSerializer<T> implements Serializer<T> {
constructor(public types: (Serializer<T>)[]) {
// next line is unnecessary with "public types" above
// this.types = types
}
serialize(value: T): ArrayBuffer {
for (const type of this.types) {
try {
return type.serialize(value) //throws error if type can't serialize value
}
catch (e) { }
}
throw new Error('No type matched')
}
}
Let me know if you need me to explain any of that. 让我知道您是否需要我解释其中的任何内容。 Hope it helps.
希望能帮助到你。 Good luck!
祝好运!
Thanks for the answer!
感谢您的回答! This was my original solution.
这是我最初的解决方案。 The problem I have though is that not all the types I pass in will necessarily be
Serializer<T>
with the sameT
.我的问题是,并不是我传递的所有类型都一定是具有相同
T
Serializer<T>
。 For example, I might have aSerializer<string>
and aSerializer<number>
, so the desiredT
for the union would bestring | number
例如,我可能有一个
Serializer<string>
和Serializer<number>
,因此联合的所需T
为string | number
string | number
but neither of the originalSerializer
s implementSerializer<string | number>
string | number
但原始Serializer
都没有实现Serializer<string | number>
Serializer<string | number>
.Serializer<string | number>
。 Does that make sense?那有意义吗?
That is an excellent point and I failed to address it. 这是一个很好的观点,我未能解决。 The short answer is that in TypeScript generic type parameters are covariant , meaning that, in fact, you can always narrow a
Serializer<string|number>
value to one of type Serializer<string>
: 简短的回答是,在打字稿泛型类型参数是协变的 ,这意味着,实际上,你总是可以缩小一个
Serializer<string|number>
值类型的一个Serializer<string>
:
declare const numberOrStringSerializer: Serializer<number | string>;
const justStringSerializer: Serializer<string> = numberOrStringSerializer;
const justNumberSerializer: Serializer<number> = numberOrStringSerializer;
(This type of narrowing is unsound in general, but TypeScript does it for reasons of performance and developer convenience. There are possible fixes for it but they're not part of the language yet.) (这种类型的缩小通常是不合理的,但是出于性能和开发人员方便的原因,TypeScript这样做是可行的 。有可能的修复方法,但它们还不是该语言的一部分。)
That being said, type inference on the UnionSerializer
constructor will not automatically infer the union type : 话虽如此,但对
UnionSerializer
构造函数的类型推断不会自动推断联合类型 :
new UnionSerializer([justNumberSerializer, justStringSerializer]); // error
You can manually specify it, however: 您可以手动指定它,但是:
new UnionSerializer<string|number>([justNumberSerializer, justStringSerializer]); // ok
This is a bit annoying, but it works. 这有点烦人,但可以。 There may be ways to improve this experience but I'm not sure at the moment.
可能有一些方法可以改善这种体验,但目前还不确定。 Does this help?
这有帮助吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.