Il existe également un problème très connexe lorsque vous souhaitez obtenir une intersection de plusieurs types, mais sans nécessairement convertir les unions en intersections. Il n'y a tout simplement aucun moyen d'accéder directement aux intersections sans recourir à des unions temporaires !
Le problème est que les types dont nous souhaitons obtenir une intersection peuvent contenir des unions, qui seront également converties en intersections. Les gardes à la rescousse :
// 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 }
L'exécution dans l'exemple donné se déroule comme suit
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 }
J'espère que cela montre également d'autres techniques utiles.