简体   繁体   English

如何为严格类型的事件发射器使用相关联合类型

[英]How to use correlated union types for a strictly typed event emitter

I'm implementing a custom event emitter module, which requires the possible event names and payloads to be defined in a generic type parameter.我正在实现一个自定义事件发射器模块,它需要在通用类型参数中定义可能的事件名称和有效负载。

The usage looks like this:用法如下所示:

interface MyEventPayloadMapping = {
  event1: string;
  event2: void;
}

const emitter = createEventEmitter<MyEventPayloadMapping>();
emitter.on('event1', (payload: string) => {});
emitter.on('event2', () => {});

The type definition of EventEmitter is the following: EventEmitter的类型定义如下:

type PayloadOneArg<T> = undefined extends T ? [T?] : [T]; 
type PayloadArgs<T> = T extends undefined ? [] : PayloadOneArg<T>; // allow zero, one optional or exactly one argument

type Listener<Arguments> = (...args: PayloadArgs<Arguments>) => void;

interface EventEmitter<T> {
    on<K extends keyof T>(eventName:K, listener: Listener<T[K]>): void;
}

Now I've encountered an issue when using multiple instances of EventEmitter which partially overlap with their event payload mapping.现在我在使用多个EventEmitter实例时遇到了一个问题,这些实例与其事件有效负载映射部分重叠。

interface PayloadA {
    readonly foo: number;
    readonly bar: string;
}

interface PayloadB {
    readonly foo: number;
    readonly baz: boolean;
}

type EmitterA = EventEmitter<PayloadA>;
type EmitterB = EventEmitter<PayloadB>;

interface ModuleA {
    readonly type: 'a';
    readonly on: EmitterA['on'];
}

interface ModuleB {
    readonly type: 'b';
    readonly on: EmitterB['on'];
}

function doStuff(mod: ModuleA | ModuleB): void {
    if (mod.type === 'a') {
      mod.on('foo', () => {}); // works
    } else {
      mod.on('foo', () => {}); // works
    }
    mod.on('foo', () => {}); // doesn’t work
}

This produces the error这会产生错误

This expression is not callable.
  Each member of the union type '(<K extends keyof PayloadA>(eventName: K, listener: Listener<PayloadA[K]>) => void) | (<K extends keyof PayloadB>(eventName: K, listener: Listener<PayloadB[K]>) => void)' has signatures, but none of those signatures are compatible with each other.

After creating issue at the typescript project it was pointed out that an approach to solve this has been implemented in this pull request .typescript 项目中创建问题后,有人指出,在此拉取请求中已经实施了解决此问题的方法。 I have some troubles understanding the description of this pull request and applying the concept to my use-case.我在理解这个拉取请求的描述并将这个概念应用于我的用例时遇到了一些麻烦。

My first attempt is the following:我的第一次尝试如下:

type EventNameToListenerMap<T> = { [P in keyof T]: { name: P, listener: Listener<T[P]>} }

interface EventEmitter<T> {
    on<K extends keyof T>(eventName: EventNameToListenerMap<T>[K]['name'], listener: EventNameToListenerMap<T>[K]['listener']): void;
}

But I still get the same error.但我仍然得到同样的错误。 Here is a playground link. 是一个游乐场链接。

So the question is, can someone explain the concept of the solution introduced by the typescript pull request?那么问题来了,有人能解释一下 typescript 拉取请求引入的解决方案的概念吗? And maybe additionally provide some hints how it could be applied for my use-case?也许还提供一些提示如何将其应用于我的用例?

The general approach to this sort of thing, as described in microsoft/TypeScript#47109 , is to make some simple mapping interface likemicrosoft/TypeScript#47109中所述,此类事情的一般方法是制作一些简单的映射接口,例如

interface PayloadMap {
    a: PayloadA,
    b: PayloadB
}

and then represent your operation as a generic function over aa key type parameter K constrained to the keys of that mapping interface, and where the parameters to this function are represented as a distributive object type that is defined in terms of this mapping interface.然后将您的操作表示为通用function 上的键类型参数K 约束到该映射接口的键,并且此 function 的参数表示为分布 ZA8CFDE6331BD4B62AC96F8911 中定义的映射接口类型 Like this:像这样:

type Module<K extends keyof PayloadMap = keyof PayloadMap> = { [P in K]:
    { readonly type: P, readonly on: OnEventEmitter<PayloadMap[P]> }
}[K]

where I've defined OnEventEmitter as我将OnEventEmitter定义为

interface OnEventEmitter<T> {
    <P extends keyof T>(
        eventName: P,
        listener: (...args: PayloadArgs<T[P]>) => void
    ): void;
}

The Module<K> type is a distributive object type because it is a mapped type into which we immediately index into . Module<K>类型是分布式 object 类型,因为它是我们立即索引到映射类型 When K is "a" then you get your ModuleA type, and when K is "b" then you get your ModuleB type.K"a"时,您将获得ModuleA类型,而当K"b"时,您将获得ModuleB类型。 And, importantly, the type Module by itself is therefore a union corresponding exactly to your original ModuleA | ModuleB而且,重要的是, Module类型本身就是一个与原始ModuleA | ModuleB完全对应的联合 ModuleA | ModuleB type. ModuleA | ModuleB类型。

Anyway, now you can write doStuff() and it compiles:无论如何,现在您可以编写doStuff()并编译:

function doStuff<K extends keyof PayloadMap>(mod: Module<K>): void {
    mod.on('foo', (x) => { x.toFixed() }); // okay
}

And you can call it, even with a union of ModuleA and ModuleB input:即使使用ModuleAModuleB输入的联合,您也可以调用它:

declare const moduleA: Module<"a">
declare const moduleB: Module<"b">
doStuff(Math.random() < 0.5 ? moduleA : moduleB);

The doStuff() implementation works because mod.on() is seen to be of a single generic function type <"foo">(eventName: "foo", listener: (...args: PayloadArgs<PayloadMap[K]["foo"]>) => void) => void . doStuff()实现有效,因为mod.on()被视为单个通用 function 类型<"foo">(eventName: "foo", listener: (...args: PayloadArgs<PayloadMap[K]["foo"]>) => void) => void If doStuff() were not generic, or if Module<K> were expressed as some type that was opaque to the compiler (such as Extract<ModuleA | ModuleB, {type: K}> ), then you'd get compile errors trying to call mod.on() :如果doStuff()不是通用的,或者如果Module<K>被表示为某种对编译器不透明的类型(例如Extract<ModuleA | ModuleB, {type: K}> ),那么你会尝试编译错误调用mod.on()

// Don't do this
type OpaqueModule<K extends keyof PayloadMap> =
    Extract<Module<"a"> | Module<"b">, { type: K }>

function doOpaqueStuff<K extends keyof PayloadMap>(mod: OpaqueModule<K>): void {
    mod.on('foo', (x) => { x.toFixed() }); // error
    // none of those signatures are compatible with each other
}

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM