在TypeScript中,如何获取值为给定类型的对象类型的键?

在TypeScript中,如何获取值为给定类型的对象类型的键?,typescript,generics,typescript-generics,mapped-types,Typescript,Generics,Typescript Generics,Mapped Types,我一直在尝试创建一个类型,它由T类型的键组成,这些键的值是字符串。在伪代码中,它将是T的键,其中T[P]是一个字符串 我能想到的唯一方法是分两步进行: // 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 }

我一直在尝试创建一个类型,它由
T
类型的键组成,这些键的值是字符串。在伪代码中,它将是T的
键,其中T[P]是一个字符串

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

// 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>;
键的唯一允许值实际上应该是
“id”
,而不是
“id”|“price”|“other”
,因为其他两个键的值不是字符串

这可以通过和来完成,如下所示:

type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
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'
键匹配➡
{[K in keyof Thing]-?:Thing[K]扩展字符串?K:never}[keyof Thing]➡
{ 
id:字符串扩展字符串?'id':从不;
价格:数字扩展字符串?“数字”:从不;
其他:{stuff:boolean}扩展字符串?'other':从不;
}[“id”|“price”|“other”]➡
{id:'id',price:never,other:never}['id'|'price'|'other']➡
“id”|从不|从不➡
“身份证”

请注意,您正在做的是:

type SetNonStringToNever<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };
type SetNonStringToNever={[P in keyof T]:T[P]扩展字符串?T[P]:never};
实际上只是将非字符串属性值转换为
never
属性值。它没有碰钥匙。你的
东西将变成
{id:string,price:never,other:never}
。它的键与
东西的键相同。这与
KeysMatching
的主要区别在于,您应该选择键,而不是值(因此
P
而不是
T[P]


希望有帮助。祝你好运

作为补充回答:

从版本4.1开始,您可以利用它来替代解决方案(请注意,核心逻辑与jcalz没有区别)。只需筛选出在用于索引源类型时不会产生可分配给目标类型的类型的键,并提取剩余键与
keyof
的并集:

type KeysWithValsOfType=keyof{[P在keyof T中作为T[P]扩展V?P:never]:P};
接口事物{
id:字符串;
价格:数量;
测试:数字;
其他:{stuff:boolean};
}
类型keys1=KeysWithValsOfType//id->ok
类型keys2=KeysWithValsOfType//价格|测试->确定


正如以下人士正确提到的:

两者都可以提取字符串键的并集。然而,当他们应该在更复杂的情况下使用-如T扩展键。。。那么TS就不能很好地“理解”您的解决方案

因为上面的类型不使用
keyof T
索引,而是使用映射类型的
keyof
,编译器无法推断
T
可由输出联合索引。为了确保编译器了解这一点,可以将后者与
keyof T
相交:

type KeysWithValsOfType=keyof{[P在keyof T中作为T[P]扩展V?P:never]:P}&keyof T;
函数getNumValueC(thing:T,key:K){
返回东西[键];//好的
}

可能是重复的,或者至少我的答案是一样的——这似乎确实有效!你能简单地解释一下为什么这个方法有效而我的尝试无效吗?好的,这是有道理的,尽管我不确定我自己是否能想出这个方法。谢谢@jcalz!虽然这个答案和来自jcalz的答案生成相同的类型,但是来自jcalz的答案更好,因为Typescripts记住结果键来自原始对象T,并且可以在以后使用它来索引它,就像在T[K]中一样。有了这个答案,TS 4.3不知道并发出错误“类型“K”不能用于索引TS 4.3中的类型“T”。TS(2536)?我以为我们几乎没有4.2.3(可能只是一个拼写错误)Re:indexing-我在这里遗漏了什么吗(),你能添加一个到游乐场的链接来看看吗?请看它显示了这两种解决方案的用法(来自jcalz和你的)。两者都可以提取字符串键的并集。然而,当它们应该在更复杂的情况下使用时,比如
T扩展键…
那么TS就不能很好地“理解”您的解决方案。也许这值得报道。我注意到jcalz解决方案在《夜间》中甚至更好,它可以理解在和对象上使用的计算键的属性类型。@MichalMinich-谢谢,我想你永远不会回到那个:)今天将仔细查看,但是,似乎使用
keyof T
索引确实让编译器知道结果类型是
keyof T
,而在我的版本中,由于使用映射类型的
keyof
,编译器不知道这一点。看起来不像一个bug,我理解背后的逻辑。我认为有两种解决办法。第一个是与
keyof T
相交,以确保编译器安全。第二个-
提取
。这两种方法都有效,我将修改答案,但后者显然更加冗长,几乎没有好处,因此我会坚持使用
keyof T
intersection,它更优雅。但这可能是一个太多的
keyof
s:)
type SetNonStringToNever<T> = { [P in keyof T]: T[P] extends string ? T[P] : never };