如何根据值过滤记录键?
How to filter record keys based on their values?
我有这个记录:
interface TheRecord extends TheRecordType {
a: { typeA: 'string' },
b: { typeB: 123 },
c: { typeA: 'string' },
}
type TheRecordType = Record<string, TypeA | TypeB>
type TypeA = { typeA: string }
type TypeB = { typeB: number }
我希望我的函数只接受值为 A 类型的键
doStuff('b'); //this should fail
function doStuff(arg: keyof FilteredForTypeA): void {
...
}
以下是我尝试过滤掉它们的方法
type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
这里发生了一些事情,所以我会做出回答,因为它不是我发现的相关现有问题的直接副本。
当您的类型具有索引签名时,如果它们是索引签名的子类型,则很难仅提取对象的 "known" 文字键。也就是说,keyof {[k: string]: any, foo: any}
只是 string
,"foo"
完全包含在其中。您可以使用条件类型技巧来仅提取已知的文字键,如 :
所示
type KnownKeys<T> = Extract<{
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never, keyof T>;
另一方面,您只需要其值具有 属性 匹配特定类型的键。这可以通过映射条件查找来实现,如 :
所示
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
把它们放在一起,你会得到:
type KnownKeysMatching<T, V> = KeysMatching<Pick<T, KnownKeys<T>>, V>
并且您可以验证它是否按照我认为的那样工作:
function doStuff(arg: KnownKeysMatching<TheRecord, TypeA>): void {
}
doStuff('a'); // okay
doStuff('b'); // error!
doStuff('c'); // okay
doStuff('d'); // error!
请注意 arg
不能是 'b'
,正如所希望的那样,但它也不能是 'd'
或任何其他 "unknown" 字符串,即使 TheRecord
有一个字符串索引签名。如果您需要 'd'
的一些其他行为,那是可以做到的,但这似乎超出了问题的范围。
希望对您有所帮助;祝你好运!
使用略微修改的 KnownKeys
版本来排除其值也从不扩展的键,你最终会得到这个
interface TheRecord extends TheRecordType {
a: { typeA: 'string' },
b: { typeB: 123 },
c: { typeA: 'string' },
}
type TheRecordType = Record<string, TypeA | TypeB>
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : T[K] extends never ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
type TypeA = { typeA: string }
type TypeB = { typeB: number }
function doStuff(arg: KnownKeys<FilteredForTypeA>): void {
}
type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
doStuff('b'); // error!
我有这个记录:
interface TheRecord extends TheRecordType {
a: { typeA: 'string' },
b: { typeB: 123 },
c: { typeA: 'string' },
}
type TheRecordType = Record<string, TypeA | TypeB>
type TypeA = { typeA: string }
type TypeB = { typeB: number }
我希望我的函数只接受值为 A 类型的键
doStuff('b'); //this should fail
function doStuff(arg: keyof FilteredForTypeA): void {
...
}
以下是我尝试过滤掉它们的方法
type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
这里发生了一些事情,所以我会做出回答,因为它不是我发现的相关现有问题的直接副本。
当您的类型具有索引签名时,如果它们是索引签名的子类型,则很难仅提取对象的 "known" 文字键。也就是说,keyof {[k: string]: any, foo: any}
只是 string
,"foo"
完全包含在其中。您可以使用条件类型技巧来仅提取已知的文字键,如
type KnownKeys<T> = Extract<{
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never, keyof T>;
另一方面,您只需要其值具有 属性 匹配特定类型的键。这可以通过映射条件查找来实现,如
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
把它们放在一起,你会得到:
type KnownKeysMatching<T, V> = KeysMatching<Pick<T, KnownKeys<T>>, V>
并且您可以验证它是否按照我认为的那样工作:
function doStuff(arg: KnownKeysMatching<TheRecord, TypeA>): void {
}
doStuff('a'); // okay
doStuff('b'); // error!
doStuff('c'); // okay
doStuff('d'); // error!
请注意 arg
不能是 'b'
,正如所希望的那样,但它也不能是 'd'
或任何其他 "unknown" 字符串,即使 TheRecord
有一个字符串索引签名。如果您需要 'd'
的一些其他行为,那是可以做到的,但这似乎超出了问题的范围。
希望对您有所帮助;祝你好运!
使用略微修改的 KnownKeys
版本来排除其值也从不扩展的键,你最终会得到这个
interface TheRecord extends TheRecordType {
a: { typeA: 'string' },
b: { typeB: 123 },
c: { typeA: 'string' },
}
type TheRecordType = Record<string, TypeA | TypeB>
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : T[K] extends never ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
type TypeA = { typeA: string }
type TypeB = { typeB: number }
function doStuff(arg: KnownKeys<FilteredForTypeA>): void {
}
type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
doStuff('b'); // error!