[typescript] 공용체 유형을 교차 유형으로 변환

공용체 유형을 교차 유형으로 변환하는 방법이 있습니까?

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모든 consitutent가 반 변성 위치에있는 새로운 결합으로 재 포장합니다. 이렇게 I하면 핸드북에 언급 된 대로 유형을 교차로 추론 할 수 있습니다 .

마찬가지로 반 변형 위치에서 동일한 유형 변수에 대한 여러 후보가 교차 유형을 유추합니다.


작동하는지 봅시다.

먼저 저를 당신의 괄호로하자 FunctionUnionFunctionIntersection타이프 라이터는 함수의 반환보다 더 긴밀하게 결합 / 교차 결합하는 것 때문에 :

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 number true 값을 가질 수 없기 때문 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 }

바라건대 이것은 또한 다른 유용한 기술을 보여줍니다.


답변