与TypeScrip一起使用时,Object.values()无法从Record的并集派生出正确的类型:

type A = Record<string, number>;
type B = Record<string, boolean>;

function func(value: A | B) {
    const properties = Object.values(value); // any[]
}

TS Playground

我预计propertiesnumber[] | boolean[]类型,但实际类型是any[].

有没有办法从Object.values()派生出正确的类型?

推荐答案

当您调用Object.values(v)时,对于它推断的generic类型参数T,编译器try 将v{[k: string]: T}匹配(根据this call signature).

因此,如果v属于类似{[k: string]: Z}的类型,则编译器会将T推断为Z,所有操作都将成功.但如果v‘S的类型是像{[k: string]: X} | {[k: string]: Y}这样的union张唱片,情况就不同了.人们可能希望T可以被推断为联盟X | Y.但推理算法并不是以这种方式工作的.类型X | Y不会直接出现在输入类型中的任何位置,并且编译器不会synthesize个来自多个推理候选者的联合类型.取而代之的是,编译器 Select 了一个候选者.假设是X...然后,如果Record<string, X> | Record<string, Y>不可分配给Record<string, X>,则发出错误.

拒绝合成联合类型是故意的;人们通常宁愿看到错误,也不愿让编译器开始创建联合以使程序成功,特别是在许多情况下,这会使几乎每个调用都成功,即使是做像compare(3, "oops")这样疯狂的事情的调用,假设compare是像<T>(a: T, b: T) => number这样的类型.人们want看不到这一点,而不是让T被推断为number | string.

在你的情况下,你want的结合是可以推断的.在microsoft/TypeScript#44312处有一个开放的特征请求,以具有一种方式来说明应该发生这样的推断.但它还不是语言的一部分,即使它是语言的一部分,Object.values()调用签名也可能不会被更改以使用它.

因此,我们不能依赖编译器直接推断您想要的联合类型.


取而代之的是,当我们调用Object.values()时,我们可以specify并集类型.这看起来像是Object.values<X | Y>(v).在您的示例中,您可以只编写Object.values<number | boolean>(value),然后就完成了.

但您不希望以这种方式硬编码该类型参数.您希望它依赖于value,这样,如果它的类型更改为某个其他记录的联合,它将继续工作.这意味着:给定一个像{[k: string]: X} | {[k: string]: Y}这样的类型的值value,我们如何才能将类型X | Y变为compute呢?

在这里,我们可以对value使用the typeof type query operator来获得值的类型,然后我们可以用string来对该类型进行index into,以获得valuestring键处的属性的类型.

即:

Object.values<typeof value[string]>(value); // okay

或者,查看一般示例:

function foo<X, Y>() {
    type A = Record<string, X>;
    type B = Record<string, Y>;
    function func(value: A | B) {
        return Object.values<typeof value[string]>(value);        
    }
    // func(value: Record<string, X> | Record<string, Y>): (X | Y)[]
}

foo()内部,func()函数接受类型A | B的值,并返回类型(X | Y)[]的值,而无需我们自己硬编码类型X | Y.取而代之的是,typeof value[string]的计算结果是X | Y,我们使用它.

Playground link to code

Typescript相关问答推荐

等待的R没有用响应对象展开到R

ReturnType此索引方法与点表示法之间的差异

在Angular中注入多个BrowserModules,但在我的代码中只有一个

为什么我的Set-Cookie在我执行下一次请求后被擦除

Next.js错误:不变:页面未生成

记录键的泛型类型

如何从扩展接口推断泛型?

使用KeyOf和Never的循环引用

Vue3 Uncaught SyntaxError:每当我重新加载页面时,浏览器控制台中会出现无效或意外的令牌

如何 bootstrap TS编译器为类型化属性`T`推断正确的类型?

以Angular 执行其他组件的函数

声明文件中的类型继承

TypeScrip:强制使用动态密钥

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

为什么Typescript只在调用方提供显式泛型类型参数时推断此函数的返回类型?

->;Boolean类型的键不能分配给类型Never

类型判断数组或对象的isEmpty

任何导航器都未处理有效负载为";params";:{";roomId";:";...";}}的导航操作

基于属性值的条件类型

有没有一种方法可以提升对象的泛型级别,而对象的值是泛型函数?