Typescript泛型:返回数组类型,以便按位置将输入数组与映射的类型匹配



我有一个函数,可以按数据类型(例如U8、U16、F32、String等(从缓冲区中获取值。我试图弄清楚如何键入函数,这样,例如,如果我传入['u8','u16','u16','string'],则推断出的返回类型将是[number,number,number,string]。我发现了一个类似的问题,并遵循了相同的模式,但无法得到我需要的结果。

这是代码的相关部分:

type BytesTypeMap = {
U8: number,
U16: number,
U32: number,
String: string,
Text: string,
}
export type BytesType = keyof BytesTypeMap;
type BytesTypeMapped<T> = T extends BytesType ?  BytesTypeMap[T] : never;
type BytesTypeArray<T extends BytesType[]> = {
[K in keyof T]: BytesTypeMapped<T[K]>;
}
export class MessageBuffer {
read<T extends BytesType[]>(types:T):BytesTypeArray<T>{
return types.map(type=>{
const method = `read${type}` as const;
return this[method](); // Typescript error: Type '(string | number)[]' is not assignable to type 'BytesTypeArray<T>'
});
}
// ... methods matching those created with the template string above...
}
// GOAL
// ... create an instance of the class, etc.
messageBufferInstance.read(["U8","U16","String"]);
// Inferred response type should be [number,number,string]
// but is instead (number|string)[]

编译器不够聪明,无法理解map()如何在read()的实现中将一种类型的泛型元组转换为映射类型的元组。Array.prototype.map()方法的标准库类型是

interface Array<T> {
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}

它只将数组映射到数组,而不将元组映射到元组。。。尤其不是-BytesType-的元组到相关的-BytesTypeMapped的元组。这样的签名对于map()的这个特定调用来说是如此的特殊,以至于即使试图想出它并将其合并到Array接口中也会浪费精力。

相反,我建议接受编译器不能在这里验证类型安全的任务,通过使用类型断言来明确地告诉编译器你要对正确的类型负责:

read<T extends BytesType[]>(types: [...T]): BytesTypeArray<T> {
return types.map((type: BytesType) => {
const method = `read${type}` as const; // assuming we're using TS4.1+
return this[method]();
}) as BytesTypeArray<T>;
}

注意我们是如何返回as BytesTypeArray<T>的。它离(string | number)[]足够近。

旁白:在TS4.1引入microsoft/TypeScript#40707之前,我认为​`read${type}` as const不会工作。它很快就会出来,所以我会离开它。

因此,这涉及到函数的实现方面。现在转到呼叫方一侧:


这其中的另一部分是获得

const resp = messageBufferInstance.read(["U8", "U16", "String"]);

以推断为CCD_ 13而不是CCD_。我们可以通过更改read()方法签名来实现这一点,从而向编译器提供提示,如果可能的话,T应该是元组,而不是在调用read()时扩展为数组。

有不同的方法可以做到这一点,在TS 4.0引入可变元组类型之前,您必须这样做(请参阅microsoft/TypeScript#227179(

// read<T extends BytesType[] | [BytesType]>(types: T): BytesTypeArray<T> {

其中T的约束具有元组类型,但现在可以这样做:

read<T extends BytesType[]>(types: [...T]): BytesTypeArray<T> {

其中CCD_ 19参数是从CCD_。从调用者的角度来看,任何一种方式都应该有效:

const resp = messageBufferInstance.read(["U8", "U16", "String"]);
resp[0].toFixed(); // okay
resp[2].toUpperCase(); // okay

我更喜欢[...T]方法,因为它在实现方面更容易。


好的,所以调用者和实现都应该按预期工作。

游乐场链接到代码

最新更新