Reputation: 1607
I try to achieve a generic isEventOfType
function. In multiple places events from a x-state statemachine are defined (see Maschine1Events
, Maschine2Events
). When processing events x-states can only provide a Maschine1Events
or Maschine2Events
but can't tell exactly which event is processed.
I therefore need a method to check if the event is of a type that I expect. For example that the 'CHANGE' event is provided.
This example is fully functional, but what I fail to understand is why inside the fWithoutGeneric
function the types can't be infered.
export type Event<TType, TData = unknown> = { type: TType } & TData;
export type Value<TType> = { value: TType };
type Maschine1Events = Event<'INC'> | Event<'DEC'> | Event<'CHANGE', Value<number>>;
type Maschine2Events = Event<'INC', Value<number> | Event<'SEND_MESSAGE', Value<string>>;
type EventTypes<T extends Event<unknown>> = T['type'];
type EventOfType<T extends Event<unknown>, U extends EventTypes<T> = EventTypes<T>> = Extract<T, { type: U }>;
function isEventOfType<T extends Event<unknown>, U extends EventTypes<T> = EventTypes<T>>(
event: T,
type: U
): event is EventOfType<T, U> {
return event.type === type;
}
function fWithGenerics(event: Maschine1Events) {
if (isEventOfType<Maschine1Events, 'CHANGE'>(event, 'CHANGE')) {
const a: Event<'CHANGE', Value<number>> = event;
}
}
function fWithOutGenerics(event: Maschine2Events) {
if (isEventOfType(event, 'SEND_MESSAGE')) {
const a: Event<'SEND_MESSAGE', Value<string>> = event; //Why can't this be infered
}
}
Upvotes: 0
Views: 70
Reputation: 330411
I'd be inclined to give isEventOfType()
the following signature:
function isEventOfType<E extends { type: string }, T extends E['type']>(
event: E,
type: T
): event is Extract<E, { type: T }> {
return event.type === type;
}
I simplified the parameters a bit and removed the generic parameter defaults for the second parameter. I don't know what the point was of those, but unless you need them they are probably just a distraction.
The big change here is that the E
type of the event
parameter is now constrained to {type: string}
instead of {type: unknown}
. The string
gives the compiler a hint to infer a string literal type for the type T
of the type
parameter (see microsoft/TypeScript#10676 for details about about how inference of literal types happens).
Now the code works as you expect, I think:
function m1(event: Maschine1Events) {
if (isEventOfType(event, 'CHANGE')) {
event.value.toFixed(2); // okay
} else {
event // {type: "INC"} | {type: "DEC"}
}
}
function m2(event: Maschine2Events) {
if (isEventOfType(event, 'SEND_MESSAGE')) {
event.value.toUpperCase(); // okay
} else {
event.value.toFixed(2); // okay
}
}
Upvotes: 4