【问题标题】:How to filter record keys based on their values?如何根据值过滤记录键?
【发布时间】:2020-03-17 16:25:16
【问题描述】:

我有这个记录:

interface TheRecord extends TheRecordType {
  a: { typeA: 'string' },
  b: { typeB: 123 },
  c: { typeA: 'string' },
}

type TheRecordType = Record<string, TypeA | TypeB>

type TypeA = { typeA: string }
type TypeB = { typeB: number }

我希望我的函数只接受值为 typeA 的键

doStuff('b'); //this should fail

function doStuff(arg: keyof FilteredForTypeA): void {
  ...
}

这是我尝试过滤掉它们的方法

type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }

【问题讨论】:

标签: typescript generics types generic-programming typescript-generics


【解决方案1】:

这里发生了一些事情,所以我会回答,因为它不是我发现的相关现有问题的直接重复。

当您的类型具有索引签名时,如果它们是索引签名的子类型,则很难仅提取对象的“已知”文字键。也就是说,keyof {[k: string]: any, foo: any} 只是string,而"foo" 完全包含在其中。您可以使用条件类型技巧仅提取已知的文字键,如this related question 所示:

type KnownKeys<T> = Extract<{
    [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never, keyof T>;

另一方面,您只需要其值具有与特定类型匹配的属性的键。这可以通过映射条件查找来实现,如this related question 所示:

type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];

把它们放在一起,你会得到:

type KnownKeysMatching<T, V> = KeysMatching<Pick<T, KnownKeys<T>>, V>

并且您可以验证它是否按我认为的那样工作:

function doStuff(arg: KnownKeysMatching<TheRecord, TypeA>): void {
}

doStuff('a'); // okay
doStuff('b'); // error!
doStuff('c'); // okay
doStuff('d'); // error! 

请注意arg 不能是'b',但它也不能是'd' 或任何其他“未知”字符串,即使TheRecord 具有字符串索引签名。如果您需要 'd' 的其他行为,可以这样做,但这似乎超出了问题的范围。

希望有所帮助;祝你好运!

Link to code

【讨论】:

    【解决方案2】:

    使用 KnownKeys 的略微修改版本来排除值也从不扩展的键,你最终会得到这个

    interface TheRecord extends TheRecordType {
      a: { typeA: 'string' },
      b: { typeB: 123 },
      c: { typeA: 'string' },
    }
    
    type TheRecordType = Record<string, TypeA | TypeB>
    type KnownKeys<T> = {
      [K in keyof T]: string extends K ? never : number extends K ? never : T[K] extends never ? never : K
    } extends { [_ in keyof T]: infer U } ? U : never;
    
    type TypeA = { typeA: string }
    type TypeB = { typeB: number }
    
    function doStuff(arg: KnownKeys<FilteredForTypeA>): void {
    
    }
    
    type FilteredForTypeA = { [k in keyof TheRecord]: TheRecord[k] extends TypeA ? TheRecord[k] : never }
    
    doStuff('b'); // error!
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-31
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-10
      • 2017-04-23
      相关资源
      最近更新 更多