强制类型参数推理不对数组使用任何[](Typescript)

j2cgzkjk  于 2023-01-18  发布在  TypeScript
关注(0)|答案(1)|浏览(145)

我正在编写一个处理类型验证的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都工作?

9avjhtql

9avjhtql1#

最直接的方法是用你的第一种方式编写它,然后让编译器来推断generic类型参数,额外的约束是类型参数必须表示你所关心的类型的特定函数,这必然会使事情复杂化;很难/不可能让单个类型参数以一种方式使用显式规范,而以另一种方式使用类型推断。
让事情按你的要求工作的一种方法是添加第二个类型参数,这样你就可以只使用第一个类型参数来进行显式的规范,而使用第二个类型参数来进行类型推断:

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],这意味着Tnumber,你必须为item传入一个number参数,然后得到number[]
a2的情况下,您让编译器推断AT。对于A没有推断站点,因此仅默认为any[]。但是itemT的推断站点,因此默认为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[],这将使编译器选择第一个重载,其行为与您预期的一样。第一个重载失败,因为没有A的推理站点,因此它返回到never默认值,并且42不匹配......因此它尝试第二个重载,现在您得到了预期的T的直接推理行为。
你看这是两种相对复杂的方法,用于解决使用泛型函数来对抗TypeScript推理的预期行为这一相对复杂的问题。
Playground代码链接

相关问题