【问题标题】:Transform union type to intersection type将联合类型转换为交集类型
【发布时间】:2018-10-26 17:53:59
【问题描述】:

有没有办法将联合类型转换为交集类型:

type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void

我想对FunctionUnion 进行转换以获得FunctionIntersection

【问题讨论】:

    标签: typescript


    【解决方案1】:

    你想让联合交叉? Distributive conditional typesinference from conditional types 可以做到这一点。 (但不要认为可以进行交叉到联合,抱歉)这是邪恶的魔法:

    type UnionToIntersection<U> = 
      (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
    

    这将分配联合 U 并将其重新打包到一个新联合中,其中所有成分都处于逆变位置。这允许将类型推断为交集I,如手册中所述:

    同样,同一类型变量在逆变位置的多个候选会导致推断出交集类型。


    让我们看看它是否有效。

    首先让我把你的FunctionUnionFunctionIntersection 括起来,因为TypeScript 似乎比函数返回更紧密地绑定联合/交集:

    type FunctionUnion = (() => void) | ((p: string) => void);
    type FunctionIntersection = (() => void) & ((p: string) => void);
    

    测试:

    type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
    // inspects as 
    // type SynthesizedFunctionIntersection = (() => void) & ((p: string) => void)
    

    看起来不错!

    请注意,UnionToIntersection&lt;&gt; 通常会暴露一些 TypeScript 认为是实际联合的细节。例如,boolean 显然在内部表示为 true | false,所以

    type Weird = UnionToIntersection<string | number | boolean>
    

    变成

    type Weird = string & number & true & false
    

    在 TS3.6+ 中被急切地简化为

    type Weird = never
    

    因为不可能有string number true false的值。

    希望对您有所帮助。祝你好运!

    【讨论】:

    • 10 倍。我总是从你那里学到新的有趣的东西。我非常接近这个问题stackoverflow.com/questions/50369299/…,但确实需要一种将联合转变为交叉点的方法
    • 这个答案很棒,但我真的很难理解这部分“分配联合 U 并将其重新打包成一个新联合,其中所有成分都处于 逆变位置" 有效 :( 我无法完全掌握这个 逆变位置 部分。我认为这段代码:type Param&lt;T&gt; = T extends (arg: infer U) =&gt; void ? U : never; type InferredParams = Param&lt;((a: string) =&gt; void) | ((a: number) =&gt; void)&gt;; 应该给我string &amp; number,但它给了我string | number。你能解释一下为什么吗?
    • 这是因为条件类型中extends 之前的裸类型参数在任何联合成分中都是distributed。如果要禁用分布式条件类型,可以使用使类型参数“穿衣”的技巧,例如像这样的单元素元组:type Param&lt;T&gt; = [T] extends [(arg: infer U) =&gt; void] ? U : never;。这应该按你想要的方式工作。
    • @RanLottem 密钥是distributive conditional types。在我看来,手册解释得很好。我有expanded on it elsewhere 你需要更多信息。祝你好运!
    • @Ferrybig 看起来像一个带有条件类型和编译器标志的编译器错误,然后,请参阅here。如果还没有,可能有人应该打开一个关于它的 GitHub 问题。
    【解决方案2】:

    当您想要几种类型的交集但不一定将联合转换为交集时,还有一个非常相关的问题。如果不求助于临时工会,就无法直接到达十字路口!

    问题是我们想要得到交集的类型可能在里面有联合,它们也会被转换为交集。救援守卫:

    // union to intersection converter by @jcalz
    // Intersect<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
    type Intersect<T> = (T extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never
    
    // get keys of tuple
    // TupleKeys<[string, string, string]> = 0 | 1 | 2
    type TupleKeys<T extends any[]> = Exclude<keyof T, keyof []>
    
    // apply { foo: ... } to every type in tuple
    // Foo<[1, 2]> = { 0: { foo: 1 }, 1: { foo: 2 } }
    type Foo<T extends any[]> = {
        [K in TupleKeys<T>]: {foo: T[K]}
    }
    
    // get union of field types of an object (another answer by @jcalz again, I guess)
    // Values<{ a: string, b: number }> = string | number
    type Values<T> = T[keyof T]
    
    // TS won't believe the result will always have a field "foo"
    // so we have to check for it with a conditional first
    type Unfoo<T> = T extends { foo: any } ? T["foo"] : never
    
    // combine three helpers to get an intersection of all the item types
    type IntersectItems<T extends any[]> = Unfoo<Intersect<Values<Foo<T>>>>
    
    type Test = [
        { a: 1 } | { b: 2 },
        { c: 3 },
    ]
    
    // this is what we wanted
    type X = IntersectItems<Test> // { a: 1, c: 3 } | { b: 2, c: 3 }
    
    // this is not what we wanted
    type Y = Intersect<Test[number]> // { a: 1, b: 2, c: 3 }
    

    给定示例中的执行是这样的

    IntersectItems<[{ a: 1 } | { b: 2 }, { c: 3 }]> =
    Unfoo<Intersect<Values<Foo<[{ a: 1 } | { b: 2 }, { c: 3 }]>>>> =
    Unfoo<Intersect<Values<{0: { foo: { a: 1 } | { b: 2 } }, 1: { foo: { c: 3 } }}>>> =
    Unfoo<Intersect<{ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }>> =
    Unfoo<(({ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }) extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never> =
    Unfoo<(({ foo: { a: 1 } | { b: 2 } } extends any ? ((x: T) => 0) : never) | ({ foo: { c: 3 } } extends any ? ((x: T) => 0) : never)) extends ((x: infer R) => 0) ? R : never> =
    Unfoo<(((x: { foo: { a: 1 } | { b: 2 } }) => 0) | ((x: { foo: { c: 3 } }) => 0)) extends ((x: infer R) => 0) ? R : never> =
    Unfoo<{ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } }> =
    ({ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } })["foo"] =
    ({ a: 1 } | { b: 2 }) & { c: 3 } =
    { a: 1 } & { c: 3 } | { b: 2 } & { c: 3 }
    

    希望这也展示了一些其他有用的技术。

    【讨论】:

      【解决方案3】:

      我稍微扩展了@jcalz 的回答,以解决他描述的布尔问题。

      type UnionToIntersectionHelper<U> = (
        U extends unknown ? (k: U) => void : never
      ) extends (k: infer I) => void
        ? I
        : never;
      
      type UnionToIntersection<U> = boolean extends U
        ? UnionToIntersectionHelper<Exclude<U, boolean>> & boolean
        : UnionToIntersectionHelper<U>;
      

      这基本上可以防止它在后台将true | false 转换为true &amp; false,从而保留boolean 的性质。

      现在它会正确地说 UnionToIntersection&lt;boolean&gt;boolean,而不是 never,同时仍然正确地说 UnionToIntersection&lt;boolean | string&gt;never

      【讨论】:

        猜你喜欢
        • 2021-09-03
        • 1970-01-01
        • 1970-01-01
        • 2021-10-27
        • 2021-01-11
        • 1970-01-01
        • 2010-10-07
        • 2019-12-14
        • 1970-01-01
        相关资源
        最近更新 更多