¿Quieres unión a la intersección? Los tipos condicionales distributivos y la inferencia de tipos condicionales pueden hacer eso. (No creo que sea posible hacer una intersección a una unión, lo siento) Aquí está la magia malvada:
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
Eso distribuye el sindicato U
y lo vuelve a empaquetar en un nuevo sindicato donde todos los integrantes están en posición contraria. Eso permite inferir el tipo como una intersección I
, como se menciona en el manual:
Asimismo, múltiples candidatos para el mismo tipo de variable en posiciones contravariantes hacen que se infiera un tipo de intersección.
Veamos si funciona.
Primero déjeme poner entre paréntesis su FunctionUnion
y FunctionIntersection
porque TypeScript parece unir la unión / intersección con más fuerza que el retorno de la función:
type FunctionUnion = (() => void) | ((p: string) => void);
type FunctionIntersection = (() => void) & ((p: string) => void);
Pruebas:
type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
¡Se ve bien!
Tenga cuidado de que, en general, UnionToIntersection<>
exponga algunos detalles de lo que TypeScript piensa que es una unión real. Por ejemplo, boolean
aparentemente se representa internamente como true | false
, por lo que
type Weird = UnionToIntersection<string | number | boolean>
se convierte en
type Weird = string & number & true & false
que en TS3.6 + se reduce ansiosamente a
type Weird = never
porque es imposible tener un valor que sea string
y number
y true
y false
.
Espero que ayude. ¡Buena suerte!
type Param<T> = T extends (arg: infer U) => void ? U : never;
type InferredParams = Param<((a: string) => void) | ((a: number) => void)>;
debería darmestring & number
pero me dastring | number
. ¿Puedes explicar por qué?extends
de un tipo condicional se distribuyen entre los constituyentes de la unión. Si desea desactivar los tipos condicionales distribuidos, puede utilizar el truco de hacer que el parámetro de tipo "vestida", tales como una tupla de un solo elemento como esto:type Param<T> = [T] extends [(arg: infer U) => void] ? U : never;
. Eso debería funcionar de la manera que desee.unknown
es el resultado "correcto". Para todosA
yB
,UTI<A | B>
debería ser igualUTI<A> & UTI<B>
, ¿no? Vamos aB
sernever
y la notaA | never
esA
. Ahora tienesUTI<A> = UTI<A> & UTI<never>
para todosA
. La única forma razonable de satisfacer eso esUTI<never>
=unknown
, ya queX & unknown
es igual aX
. Si loUTI<never>
fueranever
, entoncesUTI<A> = UTI<A> & never
, pero desdeX & never
=never
, estás diciendoUTI<A> = never
para todosA
... no es genial. Puede haber casos de uso paraUTI<never>
sernever
, pero no es evidente para mí, así que tenga cuidado.También existe un problema muy relacionado cuando desea una intersección de varios tipos, pero no necesariamente convierte uniones en intersecciones. ¡No hay forma de llegar directamente a las intersecciones sin recurrir a uniones temporales!
El problema es que los tipos de los que nos gustaría obtener una intersección podrían tener uniones adentro , que también se convertirán en intersecciones. Guardias al rescate:
// 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 }
La ejecución en el ejemplo dado es así
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 }
Con suerte, esto también muestra algunas otras técnicas útiles.
fuente