在运行时在提取的函数中提取正确的联合类型
Extract correct union type at runtime inside extraced function
我尝试为将在 xstate 中处理的事件创建类型。
当前 xstate 使用 DefinedEvent
参数调用 processChangeEvent
函数。无法更改此键入内容。
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}`);
}
}
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, 'CHANGE', (castedEvent) => {
return castedEvent.value; // COMPILER ERROR :-(
});
}
function processEventOrThrowException < T extends PossibleTypes > (event: DefinedEvents, type: T, 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
是否属于正确类型
让我们定义以下助手类型和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
区分联合并抽象检查 type
属性 的操作以区分联合成员,使用 Extract
实用程序类型。有了这些,我会这样定义 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}`);
}
}
这在 type
参数的类型 T
和回调 return 值的类型 R
中都是通用的。现在您的 processChangeEvent()
应该可以正常工作了:
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event, 'CHANGE', (castedEvent) => {
return castedEvent.value
});
}
并且由于 processEventOrThrowException()
中的 R
类型,processChangeEvent()
的 return 类型被推断为 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
看起来不错。
我尝试为将在 xstate 中处理的事件创建类型。
当前 xstate 使用 DefinedEvent
参数调用 processChangeEvent
函数。无法更改此键入内容。
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}`);
}
}
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, 'CHANGE', (castedEvent) => {
return castedEvent.value; // COMPILER ERROR :-(
});
}
function processEventOrThrowException < T extends PossibleTypes > (event: DefinedEvents, type: T, 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
是否属于正确类型
让我们定义以下助手类型和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
区分联合并抽象检查 type
属性 的操作以区分联合成员,使用 Extract
实用程序类型。有了这些,我会这样定义 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}`);
}
}
这在 type
参数的类型 T
和回调 return 值的类型 R
中都是通用的。现在您的 processChangeEvent()
应该可以正常工作了:
function processChangeEvent(event: DefinedEvents) {
return processEventOrThrowException(event, 'CHANGE', (castedEvent) => {
return castedEvent.value
});
}
并且由于 processEventOrThrowException()
中的 R
类型,processChangeEvent()
的 return 类型被推断为 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
看起来不错。