我一直在尝试创建一种类型,该类型由类型为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"
,因为其他两个键的值不是字符串。
答案 0 :(得分:10)
作为补充答案:
从 4.1 版开始,您可以利用 key remapping 来获得替代解决方案(请注意,核心逻辑与 jcalz 的 answer 没有区别)。简单地过滤掉在用于索引源类型时不会产生可分配给目标类型的类型的键,并使用 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...
因为上面的类型不使用 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
}
答案 1 :(得分:6)
这可以通过conditional types和lookup types来完成,就像这样:
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]
)。
希望有帮助。祝你好运!