如何创建生成与给定类型匹配的对象键的类型 [英] How to create a type that yields keys of an object that match a given type

查看:0
本文介绍了如何创建生成与给定类型匹配的对象键的类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要一个类型,它将生成其属性与给定类型匹配的另一个类型的键:

type KeyOfType<T, U extends T[keyof T]> = keyof T; // ?? type such that T[KeyOfType<T, U>] == U

例如,考虑接口i:

interface I {
    a: string;
    b: number|string;
    c: string|number;
    d: string;
}

我需要以下等同内容:

type T1 = KeyOfType<I, string>;        // 'a'|'d'
type T2 = KeyOfType<I, string|number>; // 'b'|'c'
type T3 = KeyOfType<I, number>;        // never

我接近了以下内容:

type KeyOfTypeTest<T, U> = NonNullable<{
    [K in keyof T]: U extends T[K] ? T[K] extends U ? K : never : never;
}[keyof T]>;

如果不涉及OR类型,则此操作正常工作:

type T4 = KeyOfTypeTest<I, string>;        // 'a'|'d' as expected

但联合类型失败:

type T5 = KeyOfTypeTest<I, string|number>; // expected 'b'|'c', but got 'a'|'d'

TS Playground link

你知道我做错了什么吗?谢谢!

推荐答案

您的问题是U extends T[K] ? ... : ...检查意外为distributive conditional type。由于U是一个纯泛型类型参数,编译器将其拆分成多个联合元素,为每个联合元素计算条件类型,并生成结果的联合。在许多情况下,这是您想要的行为,但不是您想要的。

要防止这种情况发生,您必须通过对裸类型参数应用一些类型函数来覆盖它。最简单的方法是将U extends T[K] ? ...更改为[U] extends [T[K]] ? ...

type KeyOfTypeTest<T, U> = NonNullable<{
    [K in keyof T]: [U] extends [T[K]] ? T[K] extends U ? K : never : never;
}[keyof T]>;

并验证它是否执行您想要的操作:

type T4 = KeyOfTypeTest<I, string>;        // 'a'|'d' 
type T5 = KeyOfTypeTest<I, string | number>; // 'b'|'c'

细节:它将extends的两侧包装在一个元素的tuple运算符中的原因是array and tuple types are covariant in TypeScriptcovariant type operators保持extends关系;如果有协变运算符F<T>,则X extends Y当且仅当F<X> extends F<Y>。如果您使用一些非协变类型运算符,您仍然会关闭分布式条件类型检查,但也会破坏您正在进行的检查。例如,在type G<X> = (x: T)=>void中,G不是协变运算符,因此G<U> extends G<T[K]>不会以您希望的方式工作。


无论如何,我通常将这种键称为其值与某个类型匹配操作KeysMatching<T, V>,您可以在其中找到TV类型匹配的键,如this answer

重要的是要考虑是否要支持从T读取属性并将其分配给V类型的变量,还是要从V类型的值中读取并将其分配给T的属性,或者两者兼而有之。这三个选项导致三个不同的定义:

type KeysAssignableTo<T, V> =
    { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
type KeysAssignableFrom<T, V> =
    { [K in keyof T]-?: [V] extends [T[K]] ? K : never }[keyof T];
type KeysAssignableBothToAndFrom<T, V> =
    { [K in keyof T]-?: [T[K], V] extends [V, T[K]] ? K : never }[keyof T];

type X = KeysAssignableTo<I, string | 3> // "a" | "d"
// props "a" | "d" can be assigned to a variable of type string | 3

type Y = KeysAssignableFrom<I, string | 3> // "b" | "c"
// a variable of type string | 3 can be assigned to a props "b" | "c"

type Z = KeysAssignableBothToAndFrom<I, string | 3> // never
// you can't both read and write a value of type string | 3 to and from any props

您正在执行的操作与KeysAssignableBothToAndFrom相同:

type T4b = KeysAssignableBothToAndFrom<I, string> // "a" | "d"
type T5b = KeysAssignableBothToAndFrom<I, string | number> // "b" | "c"

如果你真的需要这种相互分配的能力,那太好了。否则,您可能需要考虑您的用例是否更适合其他定义之一。

Playground link to code

这篇关于如何创建生成与给定类型匹配的对象键的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆