【问题标题】:Intersection of mapped types映射类型的交集
【发布时间】:2018-05-07 23:24:24
【问题描述】:

考虑以下几点:

type Properties = {
    foo: { n: number };
    bar: { s: string };
    baz: { b: boolean };
};

declare function retrieveValues<K extends keyof Properties>(add?: K[]): Pick<Properties, K>[K];

// what happens
const x: { n: number } | { s: string } = retrieveValues(['foo', 'bar']);

// what I'm really trying to express (type error)
const y: { n: number } & { s: string } = retrieveValues(['foo', 'bar']);

有没有办法获得Pick&lt;Properties, K&gt; 的属性的交集?或者只是一种不同的方法来根据数组中相关字符串的存在来获取一组类型的交集?

【问题讨论】:

    标签: typescript mixins


    【解决方案1】:

    使用conditional typestype inference in conditional types 可以将{ n: number } | { s: string } 直接转换为{ n: number } &amp; { s: string }

    type GetKeys<U> = U extends Record<infer K, any> ? K : never
    
    type UnionToIntersection<U extends object> = {
       [K in GetKeys<U>]: U extends Record<K, infer T> ? T : never
    }
    
    type Transformed = UnionToIntersection<{ a: string } | { b: number }>
    // Transformed has type {a: string, b: number}
    

    Playground Link

    之所以有效,主要是因为条件类型分布在联合类型上。来自conditional types pull request

    检查类型是裸类型参数的条件类型称为分布式条件类型。分布式条件类型在实例化期间自动分布在联合类型上。例如,T extends U ? X : Y 的实例化具有 T 的类型参数 A | B | C 被解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

    【讨论】:

    • 最近的 TS 类型特性被设计成可以用作基元来构建各种类型转换(而不是将这些转换“硬编码”到语言中)
    • 条件类型和推断运算符的好例子。这应该是 TS >= 2.8 的最佳答案。
    • @KPD 你能解释一下为什么如果我将[K in GetKeys&lt;U&gt;] 更改为[K in keyof U] 它不再以同样的方式工作了吗?我猜这是因为在GetKeys&lt;U&gt; 的实例化中有一个条件类型表达式,所以它以某种方式分配到UnionToIntersection,但我无法弄清楚。提前谢谢你。
    • @Daniel 因为普通的keyof 运算符将只包含存在于联合的每个分支中的键。此答案中的 GetKeys 类型获取存在于联合的任何分支中的键。一个简单的例子:keyof ({a: any} | {b: any})never(因为联合的两个分支中都不存在键),但 GetKeys&lt;{a: any} | {b: any}&gt;'a' | 'b'
    • 很好的答案!希望他们有一种不那么晦涩/更直接的方式来实现这一目标。
    【解决方案2】:

    更新:这个答案最初是在 conditional types 被引入语言之前编写的。对于较新版本的 TypeScript,您确实可以transform arbitrary unions into intersections

    type UnionToIntersection<U> =
        (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
    
    declare function retrieveValues<K extends keyof Properties>(
        add?: K[]): UnionToIntersection<Properties[K]>;
    
    const x = retrieveValues(['foo', 'bar']);
    /* const x: {
        n: number;
    } & {
        s: string;
    } */
    

    或者你可以专门写一些东西来合并从一个类型中选择的属性:

    type PickMerge<T, K extends keyof T> =
        { [P in K]: { [Q in keyof T[P]]: [Q, T[P][Q]] }[keyof T[P]] }[K] extends infer U ?
        [U] extends [[PropertyKey, any]] ? { [KV in U as KV[0]]: KV[1] } : never : never
    
    declare function retrieveValues<K extends keyof Properties>(
        add?: K[]): PickMerge<Properties, K>;
    
    const x = retrieveValues(['foo', 'bar']);
    /* const x: {
        n: number;
        s: string;
    } */
    

    应要求提供更多解释。

    Playground link to code


    TS 2.7- 答案:

    没有直接的类型运算符可以将联合转换为交集,或者允许您iterate union types 并以编程方式处理这些片段。所以从表面上看,你被卡住了。

    备份,如果您允许自己从碎片构建Properties,而不是试图将碎片分开,您可以这样做:

    type InnerProperties = {
      n: number;
      s: string;
      b: boolean;
    }
    
    type OuterProperties = {
      foo: "n";
      bar: "s";
      baz: "b";
    }
    

    您可以看到OuterProperties 中的每个键如何映射到InnerProperties 中的键。 (请注意,在您的Properties 中,每个外部属性都有一个内部属性。不过,您不限于此。如果您想要,例如,"foo" 外部键对应于具有多个内部属性的东西,例如 @ 987654333@ 然后您将r: RegExp 添加到InnerProperties 并将foo: "n"|"r" 放入OuterProperties。)

    现在你可以像这样选择部分属性:

    type PickProps<P extends keyof OuterProperties = keyof OuterProperties> = {
      [K in OuterProperties[P]]: InnerProperties[K];
    }
    

    所以PickProps&lt;"foo"&gt;{n: number}PickProps&lt;"bar"&gt;{s: string}PickProps&lt;"baz"&gt;{b: boolean}。注意PickProps&lt;"foo"|"bar"&gt;{n: number; s: string},所以我们准备好了retrieveValues() 的输出类型。我们仍然需要根据InnerPropertiesOuterProperties 来定义Properties,如下所示:

    type Properties = {
      [K in keyof OuterProperties]: PickProps<K>
    }
    

    最后你可以按照你想要的方式声明这个函数:

    declare function retrieveValues<K extends keyof Properties>(add?: K[]): PickProps<K>;
    const y: { n: number } & { s: string } = retrieveValues(['foo', 'bar']);
    

    这样就行了。希望这会有所帮助。祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2015-07-04
      • 1970-01-01
      相关资源
      最近更新 更多