0

我尝试为将在 xstate 中处理的事件创建类型。

目前 xstateprocessChangeEvent使用参数调用该函数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}`);
    }
}
4

2 回答 2

1

让我们定义以下辅助类型和用户定义的类型保护函数

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}`);
  }
}

T这在type参数的类型R和回调返回值的类型中都是通用的。现在你processChangeEvent()应该按预期工作:

function processChangeEvent(event: DefinedEvents) {
  return processEventOrThrowException(event, 'CHANGE', (castedEvent) => {
    return castedEvent.value
  });
}

并且由于Rin的类型,推断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

看起来不错。

Playground 代码链接

于 2020-09-30T15:11:20.997 回答
0

游乐场链接

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属于正确类型
于 2020-09-30T14:55:57.220 回答