繁体   English   中英

打字稿泛型:返回数组类型以按位置与映射类型匹配输入数组

[英]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]方法,因为它在实现方面更容易。


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

Playground 链接到代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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