Keyof也是T型

时间:2018-04-12 12:40:10

标签: typescript

有没有办法限制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

3 个答案:

答案 0 :(得分:7)

由于TypeScript 2.8可以使用conditional typesCode)。

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**'属性仍为neverundefined。但对于任何其他类型(缺少'**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