简体   繁体   English

TypeScript类型注释可表示泛型的可变数量?

[英]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 ? 如何基于SerializerUnionSerializer编写类型声明? 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] (其中aAbB )传递给构造函数,它将推断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! 祝好运!


Update 1 更新1

@csander said : @csander

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 same T . 我的问题是,并不是我传递的所有类型都一定是具有相同T Serializer<T> For example, I might have a Serializer<string> and a Serializer<number> , so the desired T for the union would be string | number 例如,我可能有一个Serializer<string>Serializer<number> ,因此联合的所需Tstring | number string | number but neither of the original Serializer s implement Serializer<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM