基于可选参数属性函数的TypeScrip方法返回类型

人气:67 发布:2023-01-03 标签: optional-parameters type-inference typescript conditional-types

问题描述

正在尝试创建一个简单的实用程序,它将:

在给定数组中按原样返回 或基于给定的可选参数进行转换。 代码如下:
type MapperFn<T, U> =  (val: T) => U;

interface mapperOpts<T,U> {
  cb?: MapperFn<T,U>
}

interface mapper {
  map<T, U, Z extends mapperOpts<T,U>>(arr: Array<T>, opts: Z): Z extends { cb: MapperFn<T,U> } ? U[]: T[];
}

const obj: mapper = {
  map: (arr, { cb }) => {
    if (!cb) return arr;
    return arr.map(cb);
  }
}

const arr: number[] =[1,2,3];

const result = obj.map(arr, {cb: (element) => element.toString() }); // should be typed as `string[]`

const result2 = obj.map(arr, { cb: (element) => element+1 }); // should be typed as `number[]`

const result3 = obj.map(arr, {}); // should be types as `number[]`

但是,我收到错误:

Type '<T, U, Z extends mapperOpts<T, U>>(arr: T[], { cb }: Z) => T[] | U[]' is not assignable to type '<T, U, Z extends mapperOpts<T, U>>(arr: T[], opts: Z) => Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.
  Type 'T[] | U[]' is not assignable to type 'Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.
    Type 'T[]' is not assignable to type 'Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.

请注意,resultresult2被标记为unknown[],这可能意味着回调函数的参数类型推断工作不正常。

我错过了什么?

Typescript Playground

推荐答案

generic类型参数推理如何工作的详细说明似乎没有特别好的文档记录,除了now obsolete TypeScript Language Specification。

但一般来说,当编译器看到调用签名为c = func(a, b)的函数调用时,它需要尝试从值abc的类型推断出类型参数TUV。要推断U,编译器需要检查ac的类型,因为参数x和返回类型都依赖于U。因此x和返回类型是T的潜在推理站点。另一方面,尝试使用b来推断有关U的任何内容是没有希望的,因为y参数的类型根本不引用U。也就是说,y不是U的推理站点。

并不是所有的推理站点都得到平等对待,有些站点比其他站点更难推断。返回类型通常是糟糕的推理站点,因为编译器通常不知道预期的返回类型……如果您编写了const c = func(a, b);,则要求编译器推断c的类型,因此func()的返回类型是未知的。例如,您只能在c已经是const c: SomeType = func(a, b);这样的已知类型的情况下使用返回类型。

涉及类型参数的类型函数越复杂,推理站点的用处就越小。对于f<T>(x: T): void这样的类型,调用f(a)很容易将T推断为a类型。但对于g<T>(x: T[keyof T]): void这样的东西,几乎不可能从g(a)中推断出T。在前一种情况下,您是从相同类型的值推断T。很简单。在后一种情况下,从该类型的属性的并集的值推断T。甚至都不清楚你会怎么开始。其他类型函数往往介于这两个极端之间。如果遇到问题,最好的办法是简化推理站点中的类型函数。

最后,有些地方根本不用作推理点。像h<T, U extends T>(u: U): void这样的函数签名没有T的推理站点。在推断类型参数时不参考Generic constraints。也许您希望编译器从传递给h()的内容中推断出U,然后从U中推断出T,但实际情况并非如此。T肯定无法推断,并将回落到类似unknown的位置。

对于出现这种情况的问题,您可以查看microsoft/TypeScript#38183、microsoft/TypeScript#31529以及可能还有许多其他内容(我会搜索&Quot;推理站点&Quot;)。

话虽如此,我对map()方法的建议是:

interface Mapper {
  map<T, Z extends MapperOpts<T, any>>(arr: Array<T>, opts: Z):
    Z extends { cb: MapperFn<T, infer U> } ? U[] : T[];
}

前一版本中没有对U进行合理的推理。相反,我们将仅从arropts推断TZ。这很可能会成功。由此,我们可以使用Z通过显式conditional type inference提取U

让我们看看它是如何工作的:

const result = obj.map(arr, { cb: (element) => element.toString() }); // string[]
const result2 = obj.map(arr, { cb: (element) => element + 1 }); // number[]
const result3 = obj.map(arr, {}); // number[]

看起来不错!

Playground link to code

14