Typescript - estrechando una unión de tipos a partir de los tipos de retorno de una función
¿Por qué es que TypeScript no puede discriminar una unión de tipos cuyos tipos provienen de los tipos de retorno de una función, sin declarar explícitamente el tipo de retorno en la función?
Aquí no especifico los valores de retorno de las funciones creadoras de eventos y el tipo unión no se puede precisar.
enum EventType {
FOO = "foo",
GOO = "goo",
}
function createFooEvent(args: {
documentId: number | null
}) {
return {
type: EventType.FOO,
data: args
}
}
function createGooEvent(args: {
id: number
isSelected: boolean
}) {
return {
type: EventType.GOO,
data: args
}
}
type EventArgType =
| ReturnType<typeof createFooEvent>
| ReturnType<typeof createGooEvent>
function eventHandler(event: EventArgType) {
switch(event.type) {
case EventType.FOO: {
// Note that `event` contains `data` but `data`'s type is a union and has not been discriminated
event.data;
break
}
}
}
Pero si especifico los tipos de retorno de la siguiente manera, entonces se puede discriminar la unión.
function createFooEvent(args: {
documentId: number | null
}): {
type: EventType.FOO,
data: {
documentId: number | null
}} {
return {
type: EventType.FOO,
data: args
}
}
function createGooEvent(args: {
id: number
isSelected: boolean
}): {
type: EventType.GOO,
data: {
id: number
isSelected: boolean
}} {
return {
type: EventType.GOO,
data: args
}
}
Aquí hay un ejemplo en el playground de TS.
Respuesta #1
Porque TypeScript no infiere la constante como el tipo por defecto: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
Por ejemplo:
var a = 'test'
Typescript inferirá el tipo de a como string, no como 'test'.
Puedes solucionarlo usando as const:
var a = 'test' as const;
En este caso a será del tipo 'test'.
Lo mismo para tu código:
function createFooEvent(args: {
documentId: number | null
}) {
return {
type: EventType.FOO,
data: args
};
}
El tipo de retorno de la función es {type: EventType} en lugar de {type:'foo'}.
Agregar as const al tipo de retorno funcionará como esperas en TS Playground
function exampleOne(){
enum EventType {
FOO = "foo",
GOO = "goo",
}
function createFooEvent(args: {
documentId: number | null
}) {
return {
type: EventType.FOO,
data: args
} as const;
}
function createGooEvent(args: {
id: number
isSelected: boolean
}) {
return {
type: EventType.GOO,
data: args
} as const;
}
type EventArgType =
| ReturnType<typeof createFooEvent>
| ReturnType<typeof createGooEvent>
function eventHandler(event: EventArgType) {
switch(event.type) {
case EventType.FOO: {
// event.data in this case will be {documentId: number|null}
event.data;
break
}
}
}
}
