当你有一个依赖于generic类型参数的conditional type,比如RT<T>
,编译器倾向于对它求值为defer,直到指定了泛型类型参数.这意味着在知道T
是什么之前,编译器真的不知道什么可以或不可以赋值给 RT<T>
.
即使T
对于完全适合条件类型的True分支或False分支的类型是constrained时,这仍然是正确的.您可能希望或期望T
被约束为Array<unknown>
会允许编译器在指定T
之前计算T extends Array<unknown> ? { v: T } : T
到{ v: T }
的值,但这并没有发生.
是的,通用约束语法<T extends U>
与条件类型判断语法T extends U ? X : Y
相同,但不幸的是,这并不意味着编译器可以使用前者及早计算后者.
GitHub中有很多关于这方面的问题,比如microsoft/TypeScript#31096和microsoft/TypeScript#56045.由于设计上的限制,这些通常是封闭的.在可预见的future ,语言就是这样的.
所以编译器不知道{v: T}
可以在test()
的调用签名中赋值给RT<T>
,因此它不会让你返回value is RT<T>
,因为type predicates必须代表重复,而不是任意的类型变化.
如果您想继续执行这样的函数,则需要重写调用签名.一般来说,如果您知道类型U
可以赋值给类型V
,但编译器不知道,那么您可以使用intersection U & V
来代替U
,因为它知道无论U
是什么,U & V
都可以赋值给V
.如果你对可分配性的看法是正确的,那么U & V
将或多或少等同于U
.(您也可以将Extract<U, V>
与Extract
实用程序类型一起使用).这意味着我们可以写
function test<T extends Array<unknown>>(value: RT<T>): value is RT<T> & { v: T } {
return "v" in value;
}
它会编译的.这应该足以让您继续操作.
旁白:假设您的实际用例激发了类型保护功能.但从表面上看,这似乎没有什么用处.如果我们try 比编译器更积极地计算RT<T>
,并假设它必须为{v: T}
,因为T
被约束为Array<unknown>
,那么这将导致:
function test<T extends Array<unknown>>(value: { v: T }): value is { v: T } {
return "v" in value;
}
这意味着你将拨打test(value)
,以此来判断value
是否属于你已经知道的类型.也就是说,该函数完全不可能返回false
,并且没有明显的理由需要判断这一点.因此,尽管此示例足以演示如何更改类型谓词以使其被接受,但它似乎没有任何明显的目的.不过,这超出了问题的范围,所以我不会再离题了.
Playground link to code个