簡體   English   中英

如何為嚴格類型的事件發射器使用相關聯合類型

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

我正在實現一個自定義事件發射器模塊,它需要在通用類型參數中定義可能的事件名稱和有效負載。

用法如下所示:

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

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

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

現在我在使用多個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 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.

typescript 項目中創建問題后,有人指出,在此拉取請求中已經實施了解決此問題的方法。 我在理解這個拉取請求的描述並將這個概念應用於我的用例時遇到了一些麻煩。

我的第一次嘗試如下:

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

但我仍然得到同樣的錯誤。 是一個游樂場鏈接。

那么問題來了,有人能解釋一下 typescript 拉取請求引入的解決方案的概念嗎? 也許還提供一些提示如何將其應用於我的用例?

microsoft/TypeScript#47109中所述,此類事情的一般方法是制作一些簡單的映射接口,例如

interface PayloadMap {
    a: PayloadA,
    b: PayloadB
}

然后將您的操作表示為通用function 上的鍵類型參數K 約束到該映射接口的鍵,並且此 function 的參數表示為分布 ZA8CFDE6331BD4B62AC96F8911 中定義的映射接口類型 像這樣:

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

我將OnEventEmitter定義為

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

Module<K>類型是分布式 object 類型,因為它是我們立即索引到映射類型 K"a"時,您將獲得ModuleA類型,而當K"b"時,您將獲得ModuleB類型。 而且,重要的是, Module類型本身就是一個與原始ModuleA | ModuleB完全對應的聯合 ModuleA | ModuleB類型。

無論如何,現在您可以編寫doStuff()並編譯:

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

即使使用ModuleAModuleB輸入的聯合,您也可以調用它:

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

doStuff()實現有效,因為mod.on()被視為單個通用 function 類型<"foo">(eventName: "foo", listener: (...args: PayloadArgs<PayloadMap[K]["foo"]>) => void) => void 如果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 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM