有没有办法限制keyof T
,以便它只接受某种类型的密钥?假设如下:
interface Something {
id: number;
name: string;
value1: FancyType;
value2: FancyType;
value3: FancyType;
}
function someFunction(key: keyof Something) {
// do something to a FancyType
}
someFunction
会接受id | name | value1 | value2 | value3
,有没有办法将其限制为FancyType
类型的键,即value1 | value2 | value3
?
答案 0 :(得分:7)
由于TypeScript 2.8可以使用conditional types(Code)。
type FancyProperties<T> = Pick<T, {
[K in keyof T]: T[K] extends FancyType ? K : never
}[keyof T]>;
function someFunction(key: keyof FancyProperties<Something>) {
// do something to a FancyType
}
someFunction("id"); // Error
someFunction("value2"); // OK
答案 1 :(得分:3)
不幸的是,我没有在我面前安装TS 2.6.2编译器,所以我无法仔细检查这是否有效。因为它涉及对现有类型的扩充,它也不是我最喜欢的解决方案(@Motti提出的带有条件类型的2.8解决方案绝对是可行的方法)。
那就是说,这是尝试它的一种方法。我假设FancyType
是一个接口,你可以merge一个属性进入它。这是:
interface FancyType {
'**fancyBrand**'?: never
}
因此,类型FancyType
的值现在称为可选属性,其键名为**fancyBrand**
且其值为不可能的never
类型。实际上,这意味着您不需要创建此类属性,类型FancyType
的现有值不会导致任何类型错误。
现在有一些辅助类型的功能。 ValueOf<T>
只生成对象的属性类型的并集:
type ValueOf<T> = T[keyof T];
所以ValueOf<{a: string, b: number}>
将是string | number
。并且Diff
接受两个字符串文字的联合,并从第一个中删除第二个:
type Diff<T extends string, U extends string> =
({ [K in T]: K } & { [K in U]: never } & { [k: string]: never })[T];
因此Diff<'a'|'b'|'c', 'a'>
为'b'|'c'
。
最后,我们已准备好定义FancyKeys<T>
,这是一个返回T
键的类型函数,其属性值为FancyType
:
type FancyKeys<T> = Diff<keyof T, ValueOf<
{ [K in keyof T]: (T[K] & { '**fancyBrand**': K })['**fancyBrand**'] }>>
因此FancyKeys<Something>
将是'value1'|'value2'|'value3'
:
function someFunction(key: FancyKeys<Something>) {
// do something to a FancyType
}
someFunction("id"); // error
someFunction("value2"); // okay
FancyKeys<T>
基本上将T
与其键与T
相同的类型相交,并且其值均具有等于键的'**fancyBrand**'
属性。对于FancyType
类型的属性,生成的'**fancyBrand**'
属性仍为never
或undefined
。但对于任何其他类型(缺少'**fancyBrand**'
键)的那些,结果属性将是关键。
这会将Something
变为{id: 'id', name: 'name', value1: undefined, value2: undefined, value3: undefined}
。当你为此ValueOf<T>
时,它变为'id'|'name'|undefined
。由于某种原因,我无法理解,它可以让Diff<keyof Something, U>
只生成'value1'|'value2'|'value2'
...不确定为什么它允许undefined
Diff<>
,但我没有抱怨。如果它没有让我们这样做,唯一可行的替代方案是在**fancyBrand**
中制作FancyType
属性 ,这会让你的生活比当你试图实例化它时你想要的(除非它是一个类,在这种情况下它不是一个大问题)。
所以这样可行,但对我来说感觉很难吃,而且我或多或少地放弃了使用条件类型的这种hacky疯狂。不过,希望有助于了解它。祝你好运!
答案 2 :(得分:1)
@Motti答案的更好实现是:
type FilteredKeyOf<T, TK> = keyof Pick<T, { [K in keyof T]: T[K] extends TK ? K : never }[keyof T]>;
它使用使用条件类型和never
的相同原理,但是参数化了属性应在TK
中扩展的类型。
您可以更灵活地使用它:
function someFunction(key: FilteredKeyOf<Something, FancyType>) {
// do something to a FancyType
}
someFunction("id"); // error
someFunction("value2"); // okay