问题描述
我尝试为将在xstate中处理的事件创建类型。
当前,xstate使用processChangeEvent
参数调用DefinedEvent
函数。此类型无法更改。processChangeEvent
应该始终与更改事件一起调用,因此我想找到一种方法来检查给定事件是否为更改事件,否则抛出错误。
一切都在if语句中进行。打字稿编译器实现了CHANGE
事件类型,因此我可以访问value
属性。
export type Event<TType,TData = unkNown> = { type: TType } & TData;
export type Value<TType> = { value: TType };
export type DefinedEvents = Event<'INC'> | Event<'DEC'> | Event<'CHANGE',Value<number>>;
function processChangeEventOK(event: DefinedEvents) {
if (event.type === 'CHANGE') {
return event.value; // NO COMPILER ERROR :-)
} else {
throw new Error(`Event must be of type CHANGE but is ${event.type}`);
}
}
问题是我需要很多次。因此,我尝试提取processEventOrThrowException
函数内部的逻辑。我知道某种程度上回调必须使用不同的Event<T
类型,但是我不知道如何?
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event,'CHANGE',(castedEvent) => {
return castedEvent.value; // COMPILER ERROR :-(
});
}
function processEventOrThrowException<TType>(event: Event<any>,type: string,callback: (castedEvent: Event<TType,unkNown>) => any) {
if (event.type === type) {
return callback(event);
} else {
throw new Error(`Event must be of type CHANGE but is ${event.type}`);
}
}
解决方法
让我们定义以下助手类型和user-defined type guard function:
type EventTypes = DefinedEvents['type'];
type EventOfType<T extends EventTypes> = Extract<DefinedEvents,{ type: T }>
function isEventOfType<T extends EventTypes>(
event: DefinedEvents,type: T
): event is EventOfType<T> {
return event.type === type;
}
这将使用您的DefinedEvents
所区分的联合,并使用Extract
实用程序类型来抽象检查type
属性以区分联合成员的操作。有了这些,我将像这样定义processEventOrThrowException()
:
function processEventOrThrowException<T extends EventTypes,R>(
event: DefinedEvents,type: T,callback: (castedEvent: EventOfType<T>) => R
) {
if (isEventOfType(event,type)) {
return callback(event);
} else {
throw new Error(`Event must be of type ${type} but is ${event.type}`);
}
}
在T
参数的类型type
和回调返回值的类型R
中都是通用的。现在您的processChangeEvent()
应该可以正常工作了:
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event,'CHANGE',(castedEvent) => {
return castedEvent.value
});
}
并且由于R
中的processEventOrThrowException()
类型,因此推断processChangeEvent()
的返回类型为number
:
const val = processChangeEvent({ type: "CHANGE",value: Math.PI });
console.log(val.toFixed(2)) // 3.14
processChangeEvent({ type: "DEC" }); // Error: Event must be of type CHANGE but is DEC
看起来不错。
,export type PossibleTypes = 'INC' | 'DEC' | 'CHANGE';
export type Event < TType extends PossibleTypes,TData = unknown > = {
type: TType
} & TData;
export type Value < TType > = {
value: TType
};
export type DefinedEvents < T extends PossibleTypes = PossibleTypes > =
T extends 'CHANGE' ?
Event < 'CHANGE',Value < Number >>
: Event < T >
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event,(castedEvent) => {
return castedEvent.value; // COMPILER ERROR :-(
});
}
function processEventOrThrowException < T extends PossibleTypes > (event: DefinedEvents,callback: (castedEvent: DefinedEvents < T > ) => any) {
const isCorrectEvent = (e: DefinedEvents): e is DefinedEvents < T > => event.type === type
if (isCorrectEvent(event)) {
return callback(event);
} else {
throw new Error(`Event must be of type CHANGE but is ${event.type}`);
}
}
说明
- 列出所有可能的类型
- 使用条件类型来定义
DefinedEvents
- 使用类型谓词来识别
event
是否为正确的类型