Vereinigungstyp in Schnittpunkttyp umwandeln


88

Gibt es eine Möglichkeit, einen Vereinigungstyp in einen Schnittpunkttyp umzuwandeln:

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

Ich möchte eine Transformation anwenden, um FunctionUnionzu bekommenFunctionIntersection

Antworten:


193

Sie möchten, dass sich die Vereinigung überschneidet? Verteilende bedingte Typen und Rückschlüsse auf bedingte Typen können dies tun. (Ich glaube nicht, dass es möglich ist, eine Kreuzung zu einer Vereinigung durchzuführen, sorry.) Hier ist die böse Magie:

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

Das verteilt die Gewerkschaft Uund verpackt sie in eine neue Gewerkschaft, in der sich alle Konstituenten in einer kontravarianten Position befinden. Dadurch kann der Typ als Schnittpunkt abgeleitet werden I, wie im Handbuch erwähnt:

Ebenso führen mehrere Kandidaten für dieselbe Typvariable in kontravarianten Positionen dazu, dass ein Schnittpunkttyp abgeleitet wird.


Mal sehen, ob es funktioniert.

Lassen Sie mich zuerst Ihre Klammern setzen FunctionUnionund FunctionIntersectionweil TypeScript die Vereinigung / Schnittmenge enger zu binden scheint als die Funktionsrückgabe:

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

Testen:

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

Sieht gut aus!

Achten Sie darauf, dass im Allgemeinen UnionToIntersection<>einige Details darüber angezeigt werden, was TypeScript für eine tatsächliche Vereinigung hält. Zum Beispiel booleanwird anscheinend intern als true | false, so dargestellt

type Weird = UnionToIntersection<string | number | boolean>

wird

type Weird = string & number & true & false

was in TS3.6 + eifrig auf reduziert wird

type Weird = never

weil es unmöglich ist, einen Wert zu haben, der string und number und true und ist false .

Hoffentlich hilft das. Viel Glück!


14
10x. Ich lerne immer neue und interessante Dinge von dir. Ich war sehr nah an dieser Frage stackoverflow.com/questions/50369299/…, aber ich brauche wirklich einen Weg, um die Union in eine Kreuzung zu verwandeln
— Tizian Cernicova-Dragomir

2
Diese Antwort ist genial , aber ich es wirklich schwer zu verstehen finden , wie dieser Teil „Das verteilt die Vereinigung U und packt sie in eine neue Vereinigung , in der alle die consitutents in sind kontra Position “ funktioniert :( ich nicht ganz diese erfassen kann kontra Position Teil Ich dachte, dass dieser Code: type Param<T> = T extends (arg: infer U) => void ? U : never; type InferredParams = Param<((a: string) => void) | ((a: number) => void)>;sollte mir geben, string & numberaber es gibt mir string | number. Können Sie erklären, warum?
— Mariusz Pawelski

10
Dies liegt daran, dass nackte Typparameter vor extendseinem bedingten Typ auf alle Gewerkschaftsbestandteile verteilt sind . Wenn Sie verteilte bedingte Typen deaktivieren möchten, können Sie den Trick verwenden, um den Typparameter "bekleidet" zu machen, z. B. ein Einzelelement-Tupel wie das folgende : type Param<T> = [T] extends [(arg: infer U) => void] ? U : never;. Das sollte so funktionieren, wie Sie es wollen.
— Jcalz

3
@RanLottem der Schlüssel sind verteilende bedingte Typen . Das Handbuch erklärt es meiner Meinung nach ziemlich gut. Ich habe es an anderer Stelle erweitert. Sie benötigen weitere Informationen. Viel Glück!
— Jcalz

1
@NicholasCuthbert sei vorsichtig; unknownist das "richtige" Ergebnis. Für alle Aund B, UTI<A | B>sollte gleich UTI<A> & UTI<B>, nicht wahr? Lass Bsein neverund notieren A | neverist A. Jetzt hast du UTI<A> = UTI<A> & UTI<never>für alle A. Der einzig vernünftige Weg, dies zu befriedigen, ist UTI<never>= unknown, da X & unknowngleich ist X. Wenn UTI<never>ja never, dann UTI<A> = UTI<A> & never, aber seit X & never= never, sagst du UTI<A> = neverfür alle A... nicht großartig. Es kann Anwendungsfälle für sein UTI<never>zu sein never, aber es ist nicht ersichtlich , zu mir, also seien Sie vorsichtig.
— Jcalz

6

Es gibt auch ein sehr verwandtes Problem, wenn Sie eine Kreuzung mehrerer Typen wünschen, aber nicht unbedingt Gewerkschaften in Kreuzungen konvertieren möchten. Es gibt einfach keine Möglichkeit, direkt zu Kreuzungen zu gelangen, ohne auf temporäre Gewerkschaften zurückzugreifen!

Das Problem ist, dass Typen, für die wir einen Schnittpunkt erhalten möchten, möglicherweise Gewerkschaften enthalten , die ebenfalls in Schnittpunkte umgewandelt werden. Wachen zur Rettung:

// 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 }

Die Ausführung im angegebenen Beispiel sieht folgendermaßen aus

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 }

Hoffentlich zeigt dies auch einige andere nützliche Techniken.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.