简体   繁体   English

在 TypeScript 中,如何获取值属于给定类型的 object 类型的键?

[英]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] )。

Playground link to code Playground 链接到代码

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

Playground 操场


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
}

Updated Playground 更新的游乐场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM