我有以下简化的代码:

export type RT<T> = T extends Array<unknown> ? {v: T} : T;
function test<T extends Array<unknown>>(value: RT<T>): value is {v: T} {
    return "v" in value;
} 

它给了我一个错误:

类型谓词的类型必须可分配给其参数的类型. 类型‘{v:t;}’不可赋值给类型‘rt’

不能将类型{V:t;}赋值给类型RT.即使它是有条件的,它也必须是可分配的.

我必须保留仿制药.

也许我应该使用不同的方法,问题是我需要判断并将"树"类型转换为不同类型的"叶子".

推荐答案

当你有一个依赖于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#31096microsoft/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

Typescript相关问答推荐

TypScript中的算法运算式

即使子网是公共的,AWS CDK EventBridge ECS任务也无法分配公共IP地址

如何使类型只能有来自另一个类型的键,但键可以有一个新值,新键应该抛出一个错误

使用React处理Websocket数据

动态调用类型化函数

为什么从数组中删除一个值会删除打字错误?

为什么TypeScript失go 了类型推断,默认为any?''

路由链接不会导航到指定router-outlet 的延迟加载模块

在类型脚本中创建泛型类型以动态追加属性后缀

保护函数调用,以便深度嵌套的对象具有必须与同级属性函数cargument的类型匹配的键

如何按属性或数字数组汇总对象数组

如何将spread operator与typescripts实用程序类型`参数`一起使用

如果判断空值,则基本类型Gaurd不起作用

在单独的组件中定义React Router路由

从以下内容之一提取属性类型

使用Cypress测试从Reaction受控列表中删除项目

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

使用NextJS中间件重定向,特定页面除外

为什么TS条件返回要求不明确的强制转换而不是精确的强制转换?

Next.JS-13(应用程序路由)中发现无效的中间件