基于可选参数属性函数的TypeScrip方法返回类型 [英] Typescript method return type based on optional param property function
问题描述
正在尝试创建一个简单的实用程序,它将:
- 在给定数组中按原样返回
- 或基于给定的可选参数进行转换。 代码如下:
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[]'.
请注意,result
和result2
被标记为unknown[]
,这可能意味着回调函数的参数类型推断工作不正常。
我错过了什么?
推荐答案
generic类型参数推理如何工作的详细说明似乎没有特别好的文档记录,除了now obsolete TypeScript Language Specification。
但一般来说,当编译器看到调用签名为c = func(a, b)
的函数调用时,它需要尝试从值a
、b
和c
的类型推断出类型参数T
、U
和V
。要推断U
,编译器需要检查a
和c
的类型,因为参数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
进行合理的推理。相反,我们将仅从arr
和opts
推断T
和Z
。这很可能会成功。由此,我们可以使用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[]
看起来不错!
这篇关于基于可选参数属性函数的TypeScrip方法返回类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!