Búsqueda de sitios web

Tipo condicional de Typescript con unión discriminada


Estoy usando una biblioteca que tiene el siguiente tipo definido:

type VoidableCallback<EventValue> = EventValue extends void ? () => void : (val: EventValue) => void;

La biblioteca expone una función que devuelve el tipo anterior:

declare function fn<T>(): VoidableCallback<T>;

Quiero usar esta función con una unión discriminada:

type Action = { type: 'add'; n: number } | { type: 'multiply'; n: number };

const callback = fn<Action>();
callback({ type: 'add', n: 1 });

Sin embargo, Typescript (3.4.1) me está dando este mensaje de error:

El tipo '"add"' no es asignable al tipo '"add" & "multiply"'. El tipo '"add"' no es asignable al tipo '"multiply"'.ts(2322)

El tipo esperado proviene de la propiedad 'type' que se declara aquí en el tipo '{ type: "add"; n: number; } & { type: "multiply"; n: number; }'

No entiendo por qué esto es así: parece que el tipo suma (unión) se está interpretando como un tipo 'producto'.

Si cambio la definición del tipo a:

type VoidableCallback<EventValue> = (val: EventValue) => void;

... TypeScript no se queja. Así que tiene que ver con el tipo condicional y los tipos de unión.

Si pudiera entender qué está pasando aquí, entonces tal vez podría hacer un PR a la biblioteca (rxjs-hooks).

Respuesta #1

Esto es causado por el comportamiento distributivo de los tipos condicionales. Los tipos condicionales se distribuyen sobre parámetros de tipo desnudos. Esto significa que si el parámetro de tipo contiene una unión, el tipo condicional se aplicará a cada miembro de la unión y el resultado será la unión de todas las aplicaciones. Así que en tu caso obtendríamos VoidableCallback<{ type: 'add'; n: number } | { type: 'multiply'; n: number }>=VoidableCallback<{ type: 'add'; n: number }> | VoidableCallback<{ type: 'multiply'; n: number }>=((val: { type: 'add'; n: number }) => void) | ((val: { type: 'multiply'; n: number }) => void) Puedes leer sobre este comportamiento aquí

La razón por la que obtienes un error sobre la intersección es la forma en que TypeScript maneja las uniones de firmas de funciones, básicamente requiere que los parámetros sean compatibles con todas las firmas en la unión, por lo que el parámetro debe ser una intersección de todos los tipos de parámetros posibles. Puedes leer sobre esto aquí

La solución simple es deshabilitar la distribución para el tipo condicional. Esto se hace fácilmente colocando el parámetro de tipo en una tupla:

type VoidableCallback<EventValue> = [EventValue] extends [void] ? () => void : (val: EventValue) => void;
type Action = { type: 'add'; n: number } | { type: 'multiply'; n: number };
declare function fn<T>(): VoidableCallback<T>;
const callback = fn<Action>();
callback({ type: 'add', n: 1 }); //ok now

Enlace del parque

Artículos relacionados