假设在我的代码库中,我有两个不同的EventEmitter实现. 例如,如下所示:

type HandlerA = (data: boolean) => void;

type HandlerB = (data: number) => void; // HandlerB is somehow different from HandlerA

type EventEmitterA = {
  on(eventName: string, handler: HandlerA): void;
  ...
};

type EventEmitterB = {
  on(eventName: string, handler: HandlerB): void;
  ...
};

现在,我们还假设我有一个能够同时使用这两种实现的JS模块:

类似于:

class EventDisposer {
  registerEvent(sender, eventName, handler) {
    sender.on(eventName, handler);
  }
  ...
}

该模块在整个代码库(TS和JS的混合)中使用. 我想把这个模块从JS转换成TS.

基本try :

type Sender = EventEmitterA | EventEmitterB;

type Handler = HandlerA | HandlerB;

class EventDisposer {
  registerEvent(sender: Sender, eventName: string, handler: Handler) {
    sender.on(eventName, handler);
  }
}

这不起作用是因为: Argument of type 'Handler' is not assignable to parameter of type 'HandlerA & HandlerB'.

What I would like to do is infer the type of Handler based on the type of the Sender (Emitter) so I don't need to change anything on the caller side. 类似于:

type ExtractHandler<S extends Sender> = S extends EventEmitterA ? HandlerA : HandlerB;

type handlerA = ExtractHandler<EventEmitterA>;

type handlerB = ExtractHandler<EventEmitterB>;

class EventDisposer2 {
  registerEvent<S extends Sender, H = ExtractHandler<S>>(sender: S, eventName: string, handler: H) {
    sender.on(eventName, handler);
  }
}

但这也不起作用,因为: Argument of type 'H' is not assignable to parameter of type 'HandlerA & HandlerB'.

是否可以根据发送者的类型缩小处理程序的类型?我想我可以用registerEvent分钟内的 typewriter .

完整示例:https://tsplay.dev/wQ8o7W

谢谢!

推荐答案

你的try 很接近,你的错误主要是操作顺序,但这里有一些提示.

首先,我将为句柄创建一个泛型处理程序,创建句柄,键入Union,然后是一个泛型emits 器,它从该并集中获取一个句柄.

其次,您的事件处理程序类可以简单地获取一个处理程序,并在内部使用它为您键入emits 器.

这样,就可以保证树中的所有内容都是单一类型,下面是我的示例:

// Generic handler (why rewrite the same type code
// multiple times when we have generics?)
type GenericHandler<T> = (data: T) => void;

type HandlerA = GenericHandler<boolean>;
type HandlerB = GenericHandler<number>;

// Handler Union
type Handlers = HandlerA | HandlerB;

// Generic emitter that uses the union
interface GenericEmitter<T extends Handlers> {
  on(eventName: string, handler: T): void
}

// Event disposer which simply takes the handler type.
class EventDisposer {
  registerEvent<T extends Handlers>(sender: GenericEmitter<T>, eventName: string, handler: T) {
    sender.on(eventName, handler);
  }
}

// Here is the example usage, you also do not
// need to add types to parameters because typescript is already aware.
new EventDisposer().registerEvent<HandlerA>({ on: (eventName, handler) => {} }, "", (data) => {});

这可能是最简单的方法.

还请注意,如果您有许多不同的处理程序,您可以总是懒惰地让函数本身自动为您推断所有类型,同时仍然保留以前的功能,而在编译方面几乎没有任何差异:

type GenericHandler<T = any> = (data: T) => void;

interface GenericEmitter<T extends GenericHandler> {
  on(eventName: string, handler: T): void
}

class EventDisposer {
  registerEvent<T extends GenericHandler>(sender: GenericEmitter<T>, eventName: string, handler: T) {
    sender.on(eventName, handler);
  }
}

// `data: boolean` creates a handler, it will infer it all for you (passing functions work too).
new EventDisposer().registerEvent({ on: (eventName, handler) => {} }, "", (data: boolean) => {});

Typescript相关问答推荐

类型脚本泛型最初推断然后设置

如何将联合文字类型限制为文字类型之一

使用ngrok解析Angular 时出现HTTP故障

泛型函数即使没有提供类型

如何提取密钥及其对应的属性类型,以供在新类型中使用?

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

为什么T[数字]不也返回UNDEFINED?

接口中函数的条件参数

如何使我的函数专门针对联合标记?

TypeError:正文不可用-NextJS服务器操作POST

如何在省略一个参数的情况下从函数类型中提取参数类型?

Angular NG8001错误.组件只能在一个页面上工作,而不是在她的页面上工作

我的类型谓词函数不能像我预期的那样工作.需要帮助了解原因

从联合提取可调用密钥时,未知类型没有调用签名

带有过滤键的 Typescript 映射类型

基于可选参数的动态类型泛型类型

如何确定单元格中的块对 mat-h​​eader-cell 的粘附(粘性)?

如何键入仅在包含特定子类型时才接受联合类型的函数?

Typescript - 如何从动态创建的对象推断正确的子类类型

TypeScript 方法包装另一个类的任何方法