[英]Typescript generics: Return array type to match input array by position with mapped types
我有一个函数可以按数据类型(例如 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
-to-tuples-of- 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"]);
被推断为[number, number, string]
而不是(string | number)[]
。 我们可以通过更改read()
方法签名来给编译器一个提示,如果可能的话, T
应该是一个元组,而不是在调用read()
时扩展为数组,从而实现这一点。
有不同的方法可以做到这一点,在 TS 4.0 引入可变元组类型之前,你必须这样做(参见microsoft/TypeScript#27179 )
// read<T extends BytesType[] | [BytesType]>(types: T): BytesTypeArray<T> {
其中T
的约束具有元组类型,但现在您可以这样做:
read<T extends BytesType[]>(types: [...T]): BytesTypeArray<T> {
其中types
参数是从T
传播的可变参数元组。 无论哪种方式都应该从调用者的角度工作:
const resp = messageBufferInstance.read(["U8", "U16", "String"]);
resp[0].toFixed(); // okay
resp[2].toUpperCase(); // okay
我更喜欢[...T]
方法,因为它在实现方面更容易。
好的,所以调用者和实现都应该按预期工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.