Reputation: 53
Why is it that Typescript cannot discriminate a type union whose types are from the return types of function, without explicitly declaring the return type on the function?
Here I do not specify the return values of the event creator functions and the union type cannot be narrowed.
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
}
}
}
But if I specify the return types as follows then the union can be discriminated.
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
}
}
Here is an example in TS playground.
Upvotes: 4
Views: 2433
Reputation: 8295
Because typescript does not infer the constant as the type by default: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
For example:
var a = 'test'
Typescript will infer type of a
as string
not as 'test'
.
You can fix it using as const
:
var a = 'test' as const;
In this case a
will be of type 'test'
.
Same for your code:
function createFooEvent(args: {
documentId: number | null
}) {
return {
type: EventType.FOO,
data: args
};
}
The return type of the function is {type: EventType}
instead of {type:'foo'}
.
Adding as const
to the return type, will work as you expect 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
}
}
}
}
Upvotes: 7