【TypeScript】union type を intersection type に変換する方法を知りたいです。

受付中 プログラミング
2024-12-24
土左衛門
        type FunctionUnion = () => void | (p: string) => void
        type FunctionIntersection = () => void & (p: string) => void
        
に変換を適用しFunctionUnionて取得したいFunctionIntersection
回答一覧
ユニオンを交差させたいですか? 分配条件型と条件型からの推論はそれを行うことができます。(ただし、交差点から結合への変換は可能だとは思わないでください。申し訳ありません)これが邪悪な魔法です。
        type UnionToIntersection<U> = 
          (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
        
これにより、ユニオンが分散さUれ、すべての構成要素が反変の位置にある新しいユニオンに再パッケージ化されます。Iこれにより、ハンドブックに記載されているように、タイプを交差点として推測できます。 それが機能するかどうか見てみましょう。 まず、括弧で囲みますFunctionUnion。TypeScriptFunctionIntersectionは、関数の戻り値よりも和集合/共通部分をより緊密にバインドしているように見えるためです。
        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<>一般に、TypeScriptが実際の結合であると考えるものの詳細を公開することに注意してください。たとえば、booleanは明らかに内部的にとして表されてtrue | falseいるので、
        type Weird = UnionToIntersection<string | number | boolean>
        
になります。
        type Weird = string & number & true & false
        
TS3.6+ではこれは熱心に削減されます
        type Weird = never
        
string and number と true and の値を持つことは不可能だからfalseです。 お役に立てば幸いです。幸運を!
土左衛門
複数のタイプの交差点が必要であるが、必ずしもユニオンを交差点に変換する必要がない場合にも、非常に関連する問題があります。一時的な組合に頼らずに交差点にたどり着く方法はありません! 問題は、交差点を取得したい型の中にユニオンが含まれている可能性があることです。これも交差点に変換されます。
        // 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 }
        
うまくいけば、これは他のいくつかの有用なテクニックも示しています。
土左衛門
        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 & false維持します。boolean UnionToIntersection<boolean>これで、正しくはboolean、ではなく、と正しく表示されますが、neverそれでも正しく表示UnionToIntersection<boolean | string>されますnever
土左衛門