繁体   English   中英

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

[英]In TypeScript, how to get the keys of an object type whose values are of a given type?

我一直在尝试创建一个类型,该类型由T类型的键组成,其值为字符串。 在伪代码中,它是keyof T where T[P] is a string

我能想到的唯一方法是分两步:

// 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>;

然而,TS 编译器说Key<T>简单地等于keyof T ,即使我已经通过将它们设置为never使用条件类型来过滤掉其值不是字符串的键。

所以它仍然允许这样做,例如:

interface Thing {
    id: string;
    price: number;
    other: { stuff: boolean };
}

const key: Key<Thing> = 'other';

key的唯一允许值真的应该是"id" ,而不是"id" | "price" | "other" "id" | "price" | "other" "id" | "price" | "other" ,因为其他两个键的值不是字符串。

链接到 TypeScript 操场中的代码示例

这可以通过条件类型索引访问类型来完成,如下所示:

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

然后你拉出属性匹配string的键,如下所示:

const key: KeysMatching<Thing, string> = 'other'; // ERROR!
// '"other"' is not assignable to type '"id"'

详细:

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'

请注意,您在做什么:

type SetNonStringToNever<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };

实际上只是将非字符串属性转换为never属性值。 它没有接触按键。 你的Thing会变成{id: string, price: never, other: never} 并且那个的键和Thing的键是一样的。 KeysMatching的主要区别在于您应该选择键,而不是值(因此P而不是T[P] )。

Playground 链接到代码

作为补充答案:

从 4.1 版开始,您可以利用密钥重映射作为替代解决方案(请注意,核心逻辑与 jcalz 的答案没有区别)。 简单地过滤掉在用于索引源类型时不会产生可分配给目标类型的类型的键,并使用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

操场


正如Michal Minich正确提到的:

两者都可以提取字符串键的并集。 然而,当它们应该在更复杂的情况下使用时——比如 T extends Keys...<T, X> 那么 TS 无法很好地“理解”你的解决方案。

因为上面的类型没有用keyof T索引,而是使用映射类型的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.

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