[英]In TypeScript, how to get the keys of an object type whose values are of a given type?
I've been trying to create a type that consists of the keys of type T
whose values are strings.我一直在尝试创建一个类型,该类型由
T
类型的键组成,其值为字符串。 In pseudocode it would be keyof T where T[P] is a string
.在伪代码中,它是
keyof T where T[P] is a string
。
The only way I can think of doing this is in two steps:我能想到的唯一方法是分两步:
// a mapped type that filters out properties that aren't strings via a conditional type
type StringValueKeys<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };
// all keys of the above type
type Key<T> = keyof StringValueKeys<T>;
However the TS compiler is saying that Key<T>
is simply equal to keyof T
, even though I've filtered out the keys whose values aren't strings by setting them to never
using a conditional type.然而,TS 编译器说
Key<T>
简单地等于keyof T
,即使我已经通过将它们设置为never
使用条件类型来过滤掉其值不是字符串的键。
So it is still allowing this, for example:所以它仍然允许这样做,例如:
interface Thing {
id: string;
price: number;
other: { stuff: boolean };
}
const key: Key<Thing> = 'other';
when the only allowed value of key
should really be "id"
, not "id" | "price" | "other"
当
key
的唯一允许值真的应该是"id"
,而不是"id" | "price" | "other"
"id" | "price" | "other"
"id" | "price" | "other"
, as the other two keys' values are not strings. "id" | "price" | "other"
,因为其他两个键的值不是字符串。
Link to a code sample in the TypeScript playground 链接到 TypeScript 操场中的代码示例
This can be done with conditional types and indexed access types , like this:这可以通过条件类型和索引访问类型来完成,如下所示:
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
and then you pull out the keys whose properties match string
like this:然后你拉出属性匹配
string
的键,如下所示:
const key: KeysMatching<Thing, string> = 'other'; // ERROR!
// '"other"' is not assignable to type '"id"'
In detail:详细:
KeysMatching<Thing, string> ➡
{[K in keyof Thing]-?: Thing[K] extends string ? K : never}[keyof Thing] ➡
{
id: string extends string ? 'id' : never;
price: number extends string ? 'number' : never;
other: { stuff: boolean } extends string ? 'other' : never;
}['id'|'price'|'other'] ➡
{ id: 'id', price: never, other: never }['id' | 'price' | 'other'] ➡
'id' | never | never ➡
'id'
Note that what you were doing:请注意,您在做什么:
type SetNonStringToNever<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };
was really just turning non-string property values into never
property values.实际上只是将非字符串属性值转换为
never
属性值。 It wasn't touching the keys.它没有接触按键。 Your
Thing
would become {id: string, price: never, other: never}
.你的
Thing
会变成{id: string, price: never, other: never}
。 And the keys of that are the same as the keys of Thing
.并且那个的键和
Thing
的键是一样的。 The main difference with that and KeysMatching
is that you should be selecting keys, not values (so P
and not T[P]
).与
KeysMatching
的主要区别在于您应该选择键,而不是值(因此P
而不是T[P]
)。
As a supplementary answer:作为补充答案:
Since version 4.1 you can leverage key remapping for an alternative solution (note that core logic does not differ from jcalz's answer ).从 4.1 版开始,您可以利用密钥重映射作为替代解决方案(请注意,核心逻辑与 jcalz 的答案没有区别)。 Simply filter out keys that, when used to index the source type, do not produce a type assignable to the target type and extract the union of remaining keys with
keyof
:简单地过滤掉在用于索引源类型时不会产生可分配给目标类型的类型的键,并使用
keyof
提取剩余键的并keyof
:
type KeysWithValsOfType<T,V> = keyof { [ P in keyof T as T[P] extends V ? P : never ] : P };
interface Thing {
id: string;
price: number;
test: number;
other: { stuff: boolean };
}
type keys1 = KeysWithValsOfType<Thing, string>; //id -> ok
type keys2 = KeysWithValsOfType<Thing, number>; //price|test -> ok
As rightfully mentioned by Michal Minich :正如Michal Minich正确提到的:
Both can extract the union of string keys.
两者都可以提取字符串键的并集。 Yet, when they should be used in more complex situation - like T extends Keys...<T, X> then TS is not able to "understand" your solution well.
然而,当它们应该在更复杂的情况下使用时——比如 T extends Keys...<T, X> 那么 TS 无法很好地“理解”你的解决方案。
Because the type above does not index with keyof T
and instead uses keyof
of the mapped type, the compiler cannot infer that T
is indexable by the output union.因为上面的类型没有用
keyof T
索引,而是使用映射类型的keyof
,编译器不能推断T
可以被输出联合索引。 To ensure the compiler about that, one can intersect the latter with keyof T
:为了确保编译器知道这一点,可以将后者与
keyof T
相交:
type KeysWithValsOfType<T,V> = keyof { [ P in keyof T as T[P] extends V ? P : never ] : P } & keyof T;
function getNumValueC<T, K extends KeysWithValsOfType<T, number>>(thing: T, key: K) {
return thing[key]; //OK
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.