如何在Typescript中实现事件总线类型建议。

huangapple go评论87阅读模式
英文:

How to implement Event bus types suggestion in Typescript

问题

Here's the translated code part:

你好,我在尝试实现[TinyEmitter](https://github.com/scottcorgan/tiny-emitter)的类型时遇到了问题。我需要实现两个方法。
第一个:

```typescript
addEventListener(e: string, (...args: any[]) => void): void;

第二个:

emit(e: string, ...args: any[]): void;

但这个解决方案没有提供事件名称和参数的建议。

我需要定义事件类型和事件参数。
类似这样:

type EventMap = [
 (e: 'event1', arg1: number, arg2: string, arg3: string) => void,
 (e: 'event2', arg1: string, arg2: string) => void,
 ...
];

实际上,我可以通过以下方式推断事件名称:

type EventParam<I extends number = number> = Parameters<EventMap[I]>[0];

这将推断事件类型 ('event1' | 'event2')
对于其他参数,我尝试了:

type EventArgs<I extends number = number> = EventMap[I] extends ((e: EventParam<I>, ...args: infer P) => any) ? P : never;

如何为 addEventListeneremit 函数实现带有类型建议的解决方案?

提前感谢任何建议。


Is there anything else you would like to know or any specific assistance you need with this code?

<details>
<summary>英文:</summary>

Hello I am facing problem when trying to implement types for [TinyEmitter](https://github.com/scottcorgan/tiny-emitter). I need to implement 2 methods.
First:

addEventListener(e: string, (...args: any[]) => void): void;


Second:

emit(e: string, ...args: any[]): void;


But this solution is not suggesting event names, and parameters.

I need to define event types, and event arguments.
Something like:

type EventMap = [
(e: 'event1', arg1: number, arg2: string, arg3: string) => void,
(e: 'event2', arg1: string, arg2: string) => void,
...
];


Actually i can infer event name by:

type EventParam<I extends number = number> = Parameters<EventMap[I]>[0];


this will infer event type `(&#39;event1&#39;&#160;|&#160;&#39;event2&#39;)`
For other parameters i tried:

type EventArgs<I extends number = number> = EventMap[I] extends ((e: EventParam<I>, ...args: infer P) => any ? P : never;


how to implement this for `addEventListener`&#160;and `emit` functions with type suggestions ?

Thank you in advance for any advice.

</details>


# 答案1
**得分**: 0

以下是您要翻译的内容:

建议的方法是创建一个实用程序接口,将事件名称(作为键)映射到相应的参数类型列表(作为值):

```typescript
interface EventArgs {
    event1: [number, string, string];
    event2: [string, string];
    // ...
}

然后,您可以将addEventListener()emit()方法定义为具有泛型K的方法,将e参数的类型约束为EventArgs的键:

interface Foo {
    addEventListener<K extends keyof EventArgs>(e: K, cb: (...args: EventArgs[K]) => void): void;
    emit<K extends keyof EventArgs>(e: K, ...args: EventArgs[K]): void;
}

我将这个接口称为Foo,因为问题中没有指定。然后,假设我们有一个生成Foo的实现:

const foo: Foo = makeFoo();

我们可以看到它的行为与期望一样:

// 正确的调用
foo.addEventListener("event1", (n, s1, s2) => {
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase()); // okay
});
foo.emit("event2", "Abc", "Def"); // okay
foo.emit("event1", Math.PI, "Abc", "Def"); // okay

// 错误的调用
foo.emit("event1", "Abc", "Def"); // error! 
foo.addEventListener("event2", (n, s1, s2) => { // error! 
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase())
});

这基本上回答了主要涉及类型的问题,但仍然可以以相当类型安全的方式实现它:

function makeFoo(): Foo {
    const listenerMap: { [K in keyof EventArgs]?: ((...args: EventArgs[K]) => void)[] } = {}
    const ret: Foo = {
        addEventListener<K extends keyof EventArgs>(e: K, cb: (...args: EventArgs[K]) => void) {
            const listeners: ((...args: EventArgs[K]) => void)[] = listenerMap[e] ??= [];
            listeners.push(cb);
        },
        emit<K extends keyof EventArgs>(e: K, ...a: EventArgs[K]) {
            const listeners: ((...args: EventArgs[K]) => void)[] = listenerMap[e] ?? [];
            listeners.forEach(cb => cb(...a))
        }
    }
    return ret;
}

它基本上只是将事件名称映射到事件监听器数组的对象映射,addEventListener将监听器推送到正确的数组中(如果需要,首先进行初始化),而emit从正确的数组中调用监听器(除非它不存在)。

英文:

The approach I'd recommend is to make a utility interface which maps the name of the event (as a key) to the corresponding list of argument types (as a value):

interface EventArgs {
    event1: [number, string, string];
    event2: [string, string];
    // ...
}

Then you can define your addEventListener() and emit() methods as being generic in K, the type of the e argument constrained to be a key of EventArgs:

interface Foo {
    addEventListener&lt;K extends keyof EventArgs&gt;(e: K, cb: (...args: EventArgs[K]) =&gt; void): void;
    emit&lt;K extends keyof EventArgs&gt;(e: K, ...args: EventArgs[K]): void;
}

I'm calling the interface Foo since it wasn't specified in the question. Then assuming we have an implementation that makes a Foo:

const foo: Foo = makeFoo();

We can see that it behaves as desired:

// Good calls
foo.addEventListener(&quot;event1&quot;, (n, s1, s2) =&gt; {
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase()); // okay
});
foo.emit(&quot;event2&quot;, &quot;Abc&quot;, &quot;Def&quot;); // okay
foo.emit(&quot;event1&quot;, Math.PI, &quot;Abc&quot;, &quot;Def&quot;); // okay

// Bad calls
foo.emit(&quot;event1&quot;, &quot;Abc&quot;, &quot;Def&quot;); // error! 
foo.addEventListener(&quot;event2&quot;, (n, s1, s2) =&gt; { // error! 
    console.log(n.toFixed(2), s1.toUpperCase(), s2.toLowerCase())
});

That essentially answers the question which is primarily about typings; still, it can be implemented like this in a fairly type-safe way:

function makeFoo(): Foo {
    const listenerMap: { [K in keyof EventArgs]?: ((...args: EventArgs[K]) =&gt; void)[] } = {}
    const ret: Foo = {
        addEventListener&lt;K extends keyof EventArgs&gt;(e: K, cb: (...args: EventArgs[K]) =&gt; void) {
            const listeners: ((...args: EventArgs[K]) =&gt; void)[] = listenerMap[e] ??= [];
            listeners.push(cb);
        },
        emit&lt;K extends keyof EventArgs&gt;(e: K, ...a: EventArgs[K]) {
            const listeners: ((...args: EventArgs[K]) =&gt; void)[] = listenerMap[e] ?? [];
            listeners.forEach(cb =&gt; cb(...a))
        }
    }
    return ret;
}

Essentially it just holds onto an object map from event names to arrays of event listeners, and addEventListener pushes the listener onto the right array (initializing it first if need be), while emit calls the listeners from the right array (unless it doesn't exist).

Playground link to code

huangapple
  • 本文由 发表于 2023年4月17日 15:39:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76032731.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定