如何创建生成与给定类型匹配的对象键的类型



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

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游乐场链接

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

您的问题是检查U extends T[K] ? ... : ...意外地是分发条件类型。 由于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'

Details:在单元素元组运算符中包装extends的两边的原因是因为数组和元组类型在 TypeScript 中是协变的,协变类型运算符保留了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>,您可以在其中找到以某种方式"匹配"VT类型的键,如本答案所示。

重要的是要考虑您是尝试支持从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"

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

操场链接到代码

最新更新