我正在编写一个处理类型验证的Typescript库。
我有这个工作代码:
type MyTypePredicate<T> = (value: any) => value is T
function createTypePredicateForArrayT<T>(item: T): MyTypePredicate<T[]> {
return ((value: any) => /* Real code here */ true) as MyTypePredicate<T[]>
}
// This can be use with or without providing the generic T.
const t1 = createTypePredicateForArrayT<number>(42)
const t2 = createTypePredicateForArrayT(42)
// Both t1 and t2 will have the type MyTypePredicate<number[]>
然而,纯粹为了与库中的一组其他函数保持一致,我更愿意让提供的泛型与返回的MyTypePredicate<T>
中的泛型类型相同。可以使用
function createTypePredicateForArrayA<A extends any[]>(item: A[number]): MyTypePredicate<A> {
return ((value: any) => /* Real code here */ true) as MyTypePredicate<A>
}
// which make this working:
const a1 = createTypePredicateForArrayA<number[]>(42)
// ...but the argument inference stops working,
const a2 = createTypePredicateForArrayA(42)
// it gives a2 the type MyTypePredicate<any[]> and not MyTypePredicate<number[]>
是否有一种方法重写第二,仍然使a1和a2工作?
直接的方法是用第一种方式编写它,让编译器推断泛型类型参数。类型参数必须代表你所关心的类型的特定函数的附加约束必然使事情复杂化;很难/不可能让一个类型参数以一种方式使用显式规范,而另一种方式使用类型推断。
让事情按照您的要求工作的一种方法是添加第二个类型参数,以便您仅使用第一个类型参数进行显式规范,第二个类型参数用于类型推断:
function createTypePredicateForArrayA<
A extends any[],
T = A[number]
>(item: T): MyTypePredicate<T[]> {
return ((value: any) => /* Real code here */ true) as MyTypePredicate<T[]>
}
让我们测试一下:
const a1 = createTypePredicateForArrayA<number[]>(42)
// const a1: MyTypePredicate<number[]>
const a2 = createTypePredicateForArrayA(42)
// const a2: MyTypePredicate<number[]>
在a1
的情况下,您显式指定A
为number[]
。由于TypeScript目前不支持部分类型参数推断(如ms/TS#26242所要求的),那么T
类型参数也被指定,而不是推断。因此,它最终回落到默认的A[number]
,这意味着T
是number
,你被迫为item
传递一个number
参数,你得到number[]
。
在a2
的情况下,你让编译器推断A
和T
。没有A
的推理站点,所以默认为any[]
。但item
是T
的推理站点,因此默认为number
,因此返回类型为number[]
。
另一种方法是给你的函数两个调用签名;一个用于类型参数推断,另一个用于手动规范。也就是说,使它成为一个重载函数:
function createTypePredicateForArrayA<A extends any[] = never>(
item: A[number]): MyTypePredicate<A>; // manual
function createTypePredicateForArrayA<T>(item: T): MyTypePredicate<T[]>; // infer
让我们测试一下:
const a1 = createTypePredicateForArrayA<number[]>(42)
// const a1: MyTypePredicate<number[]>
const a2 = createTypePredicateForArrayA(42)
// const a2: MyTypePredicate<number[]>
在a1
的情况下,您手动指定A
为number[]
,这使得编译器选择第一个重载,其行为如您所期望的。在a2
的情况下,不指定类型参数;第一次重载失败,因为没有A
的推理站点,因此它回落到默认的never
,而42
不匹配…因此,它尝试第二次重载,现在您得到了T
的直接推理行为,正如您所期望的那样。
好了;两种相对复杂的方法来解决相对复杂的问题,即与TypeScript的泛型函数推断的预期行为作斗争。
Playground链接到代码