代码之家  ›  专栏  ›  技术社区  ›  Robus

Keyof也是T型

  •  17
  • Robus  · 技术社区  · 8 年前

    有没有办法限制 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 回复  |  直到 8 年前
        1
  •  19
  •   Motti    8 年前

    由于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
    
        2
  •  8
  •   Gobius Dolhain    8 年前

    更好地实现@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
    
        3
  •  4
  •   jcalz    8 年前

    不幸的是,我面前没有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**” 财产仍然是 从不 或 undefined .但对于任何其他类型(缺少 “**fancyBrand**” 键),生成的属性将是键。

    这将 Something 进入 {id: 'id', name: 'name', value1: undefined, value2: undefined, value3: undefined} .当你 值(<);T> 因此 'id'|'name'|undefined .出于某种原因,我不太明白,它让你 Diff<keyof Something, U> 产生公正 'value1'|'value2'|'value2' 。。。不知道为什么它允许 未定义 在里面 Diff<> ,但我没有抱怨。如果它不让我们这样做,唯一可行的选择就是 **fancyBrand公司** 所有物 必需的 在里面 FancyType型 ,当您尝试实例化它时,这会使您的生活比您想要的更麻烦(除非它是一个类,在这种情况下,它不是一个大问题)。

    这是可行的,但我觉得很恶心,我或多或少放弃了这种疯狂的黑客行为,转而使用条件类型。尽管如此,希望了解它能有所帮助。祝你好运

    推荐文章