我尝试实现通用的isEventOfType
功能。在多个地方,定义了来自x状态状态机的事件(请参见Maschine1Events
,Maschine2Events
)。在处理事件时,x状态只能提供Maschine1Events
或Maschine2Events
,而不能确切地说明处理了哪个事件。
因此,我需要一种方法来检查事件是否为我期望的类型。例如,提供了“ CHANGE”事件。
该示例具有完整的功能,但是我无法理解的是为什么在fWithoutGeneric
函数中无法推断类型。
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
}
}
答案 0 :(得分:4)
我倾向于给isEventOfType()
以下签名:
function isEventOfType<E extends { type: string }, T extends E['type']>(
event: E,
type: T
): event is Extract<E, { type: T }> {
return event.type === type;
}
我稍微简化了参数,并删除了第二个参数的generic parameter defaults。我不知道这些是什么意思,但是除非您需要它们,否则它们可能只会分散您的注意力。
此处最大的变化是,E
参数的event
类型现在是constrained至{type: string}
而不是{type: unknown}
。 string
为编译器提供了一个提示,可以推断出T
参数的类型type
的{{3}}(有关如何推断文字的详细信息,请参见string literal type)。类型发生)。
现在,我认为代码可以按预期工作:
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
}
}