Type d'inférence du compilateur Typescript avec des génériques
J'essaye de réaliser une isEventOfTypefonction générique . Dans plusieurs endroits, les événements d'une machine à statuts x-state sont définis (voir Maschine1Events, Maschine2Events). Lors du traitement d'événements, les états x ne peuvent fournir qu'un Maschine1Eventsou Maschine2Eventsmais ne peuvent pas dire exactement quel événement est traité.
J'ai donc besoin d'une méthode pour vérifier si l'événement est d'un type que j'attends. Par exemple, l'événement "CHANGE" est fourni.
Cet exemple est entièrement fonctionnel, mais ce que je ne comprends pas, c'est pourquoi à l'intérieur de la fWithoutGenericfonction les types ne peuvent pas être déduits.
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
}
}
Lien Playground vers le code
Réponses
Je serais enclin à donner isEventOfType()la signature suivante:
function isEventOfType<E extends { type: string }, T extends E['type']>(
event: E,
type: T
): event is Extract<E, { type: T }> {
return event.type === type;
}
J'ai simplifié un peu les paramètres et supprimé les valeurs par défaut des paramètres génériques pour le deuxième paramètre. Je ne sais pas à quoi ça sert, mais à moins que vous n'en ayez besoin, ils ne sont probablement qu'une distraction.
Le grand changement ici est que le Etype du eventparamètre est désormais contraint à {type: string}au lieu de {type: unknown}. Le stringdonne au compilateur un indice pour déduire un type littéral de chaîne pour le type Tdu typeparamètre (voir microsoft / TypeScript # 10676 pour plus de détails sur la façon dont l'inférence des types littéraux se produit).
Maintenant, le code fonctionne comme prévu, je pense:
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
}
}
Lien Playground vers le code