简体   繁体   English

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

[英]Typescript generics: Return array type to match input array by position with mapped types

I've got a function to get values from a buffer by datatype (eg U8, U16, F32, String, etc).我有一个函数可以按数据类型(例如 U8、U16、F32、String 等)从缓冲区中获取值。 I'm trying to figure out how to type the function so that,for example, if I pass in ['u8','u16','u16','string'] the inferred return type would be [number,number,number,string] .我试图弄清楚如何键入函数,例如,如果我传入['u8','u16','u16','string']推断的返回类型将是[number,number,number,string] I found a similar question and followed the same pattern, but cannot get the result I need.我发现了一个类似的问题并遵循相同的模式,但无法得到我需要的结果。

Here's the relavent portion of the code:这是代码的相关部分:

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)[]

The compiler is not smart enough to understand that how map() converts a generic tuple of one type to a tuple of a mapped type inside the implementation of read() .编译器不够聪明,无法理解map()如何在read()的实现中将一种类型的通用元组转换为映射类型的元组。 The standard library's typing for the Array.prototype.map() method is Array.prototype.map()方法标准库类型

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

which only maps arrays-to-arrays and not tuples-to-tuples... and especially not tuples-of- BytesType -to-tuples-of-related- BytesTypeMapped .它只映射数组到数组而不是元组到元组......特别是不是元组的BytesType -to-tuples-of- BytesTypeMapped And such a signature would be so specific to this particular call of map() that even trying to come up with it and merge it into the Array interface would be wasted effort.而且这样的签名对于map()这个特定调用是如此特定,以至于即使试图提出它并将其合并Array接口中也将是白费力气。

Instead, I recommend accepting that the compiler is not up to the task of verifying type safety here, by using a type assertion to explicitly tell the compiler that you are taking responsibility for the types being correct:相反,我建议接受编译器无法在这里验证类型安全的任务,通过使用类型断言来明确告诉编译器您对正确的类型负责:

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>;
}

Notice how we are returning that as BytesTypeArray<T> .请注意我们如何将其as BytesTypeArray<T>返回。 It's close enough to (string | number)[] .它足够接近(string | number)[]

Aside: I don't think ​`read${type}` as const will work before TS4.1 introduces microsoft/TypeScript#40707 .旁白:在 TS4.1 引入microsoft/TypeScript#40707之前,我不认为​`read${type}` as const会起作用。 That's coming out soon so I'll leave it.这很快就会出来,所以我会离开它。

So that takes care of the implementation side of the function.这样就可以处理函数的实现方面。 Now to the caller's side:现在到调用方:


The other piece of this is getting另一部分是得到

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

to be inferred as [number, number, string] and not (string | number)[] .被推断为[number, number, string]而不是(string | number)[] We can get that to happen by changing the read() method signature to give the compiler a hint that T should be a tuple if possible instead of being widened to an array when you call read() .我们可以通过更改read()方法签名来给编译器一个提示,如果可能的话, T应该是一个元组,而不是在调用read()时扩展为数组,从而实现这一点。

There are different ways to do this, and before TS 4.0 introduced variadic tuple types , you had to do it this way (see microsoft/TypeScript#27179 )有不同的方法可以做到这一点,在 TS 4.0 引入可变元组类型之前,你必须这样做(参见microsoft/TypeScript#27179

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

where T 's constraint featured a tuple type, but now you can do it like this:其中T的约束具有元组类型,但现在您可以这样做:

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

where the types parameter is a variadic tuple spread from T .其中types参数是从T传播的可变参数元组。 Either way should work from the caller's perspective:无论哪种方式都应该从调用者的角度工作:

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

I prefer the [...T] method because it is easier on the implementation side.我更喜欢[...T]方法,因为它在实现方面更容易。


Okay, so both the caller and the implementation should work as expected.好的,所以调用者和实现都应该按预期工作。

Playground link to code Playground 链接到代码

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

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