JY-CONTENTS

JY

JY-CONTENTS
search

+

MENU

【Typescript】イベントエミッター

【Typescript】イベントエミッター

(DATE)

-

2021.04.13

(UPDATE)

-

2021.05.13

(CATEGORY)

-

イベントエミッター は、JavaScriptでポピュラーなデザインパターンです。
ここでは typescript の型システムを用いてより安全に使用する方法を説明します。

イベントエミッターについて

イベントエミッターについて簡単に説明しておきます。
イベントエミッターは、「イベント駆動のプログラミング」を行う際に使われるデザインパターンで、「イベント駆動のプログラミング」とは、順次に処理が実行されるのではなく、なんらかのイベントによってあらかじめ指定された処理が実行されるようなプログラミングの手法です。

使い方

イベントを送信(発火)させる emit メソッドと送信(発火)されたイベントを受け取り、何かの処理をする on メソッドを使います。

// イベントを送信します
emit(eventName:string, value:unknown): void 
// イベントが送信されたときに何かを行います
on(eventName: string, f:(value: unknown) => void): void

基本的な例

// イベントエミッターを読み込む
let EventE = require('events').EventEmitter;

// インスタンスを作成する
let eveIns = new EventE();

//*********************************
//          例1
//*********************************

// イベントを受け取る側--------------
eveIns.on('event', data => {
    // イベント発火後の処理を記述
    console.log('onメソッド:', data);
})
// イベントを受け取る側(1回限り)
eveIns.once('event', data => {
    // イベント発火後の処理を記述
    console.log('onceメソッド:', data);
});

// イベントを送信(発火)側----------
eveIns.emit('event', 1);
eveIns.emit('event', 2);
eveIns.emit('event', 3);

// 出力
// onメソッド: 1
// onceメソッド: 1
// onメソッド: 2
// onメソッド: 3


//********************************* 
//       例2
//*********************************

eveIns.on('ready', data => {
    console.log('ready:', data)
})

eveIns.on('error', e => {
    console.error('error:', e.message)
})

eveIns.on('reconnecting', paras => {
    console.error('reconnecting:', paras)
})


eveIns.emit('ready', 1)
eveIns.emit('error', new Error('エラー!'))
eveIns.emit('reconnecting', { attempt: 10, delay: 20 })

// 出力
// ready: 1
// error: エラー!
// reconnecting: { attempt: 10, delay: 20 }

型付けして安全に使う

上記サンプルコードの例2を型付けしていきたいと思います。
以下は、イベント名とコールバックの引数の型の定義です。

type Events = {
    ready: void
    error: Error
    reconnecting: { attempt: number, delay: number }
}

次に on メソッド に Events 型をマッピングしていきます。

type EventsMap = {
    on<E extends keyof Events>(
        event: E,
        f: (arg: Events[E]) => void
    ): void
}

// 上記は以下のような展開になります。
// type EventsMap = {
//     on<E extends 'ready' | 'error' | 'reconnecting'>(
//         event: E,
//         f: (arg: Events[E]) => void
//     ): void
// }
//  ▼
//  ▼
// // 型がオーバーロードされている
// type EventsMap = {
//     on(
//         event: 'ready',
//         f: (arg: Events['ready']) => void
//     ): void
//     on(
//         event: 'error',
//         f: (arg: Events['error']) => void
//     ): void
//     on(
//         event: 'reconnecting',
//         f: (arg: Events['reconnecting']) => void
//     ): void
// }
//  ▼
//  ▼
// type EventsMap = {
//     on(
//         event: 'ready',
//         f: (arg: void) => void
//     ): void
//     on(
//         event: 'error',
//         f: (arg: Error) => void
//     ): void
//     on(
//         event: 'reconnecting',
//         f: (arg: { attempt: number, delay: number }) => void
//     ): void
// }

同じように emit メソッドにもマッピングします。

type EventsMap = {
    on<E extends keyof Events>(
        event: E,
        f: (arg: Events[E]) => void
    ): void

    emit<E extends keyof Events>(
        event: E,
        arg: Events[E]
    ): void
}

エミッターがこの方法で型付けされていると、キーをスペルミスしたり、引数を間違って入力したり、引数を渡し忘れてしまったりすることがありません。リッスンする可能性のあるイベントと、それらのイベントのコールバック内のパラメーターの型を、typescript のコードエディターが提示してくれます。

【typescript】マップ型

型を使ってみる

let EventE = require('events').EventEmitter;
let eveIns:EventLListenerMap = new EventE();

// 定義していないイベント名なのでエディタから警告
eveIns.on('event', arg => {
    console.log(arg)
})

// コールバックの引数の型が違うのでエディタから警告
eveIns.on('ready', arg => {
    console.log(arg.message)
})

// OK(警告なし)
eveIns.on('error', arg => {
    console.log(arg.message)
})



// OK(警告なし)
eveIns.emit('ready', undefined)
// 第2引数の型が違うのでエディタから警告
eveIns.emit('error', 1)
// 第2引数の型が違うのでエディタから警告
eveIns.emit('reconnecting', { attempt: "10", delay: "20" })
// OK(警告なし)
eveIns.emit('reconnecting', { attempt: 10, delay: 20 })

上記のように型付けする事によって、間違えた記述をした場合エディタから警告が発せられるので、より安全に、正確に使う事ができます。

まとめ

全コードです。

type Events = {
    ready: void
    error: Error
    reconnecting: { attempt: number, delay: number }
}

type EventLListenerMap = {
    on<E extends keyof Events>(
        event: E,
        f: (arg: Events[E]) => void
    ): void

    emit<E extends keyof Events>(
        event: E,
        arg: Events[E]
    ): void
}

let EventE = require('events').EventEmitter;
let eveIns:EventLListenerMap = new EventE();

// 定義していないイベント名なのでエディタから警告
eveIns.on('event', arg => {
    console.log(arg)
})

// コールバックの引数の型が違うのでエディタから警告
eveIns.on('ready', arg => {
    console.log(arg.message)
})

// OK(警告なし)
eveIns.on('error', arg => {
    console.log(arg.message)
})



// OK(警告なし)
eveIns.emit('ready', undefined)
// 第2引数の型が違うのでエディタから警告
eveIns.emit('error', 1)
// 第2引数の型が違うのでエディタから警告
eveIns.emit('reconnecting', { attempt: "10", delay: "20" })
// OK(警告なし)
eveIns.emit('reconnecting', { attempt: 10, delay: 20 })

WindowEventMap

以下は WindowEventMap の型付けしているコードです。

interface WindowEventMap extends GlobalEventHandlersEventMap {
    // ... 
    contextmenu: PointerEvent
    dblclick: MouseEvent
    devicelight: DeviceLightEvent
    devicemotion: DeviceMotionEvent
    deviceorientation: DeviceOrientationEvent
    drag: DragEvent
    // ... 
}

interface Window extends EventTarget, WindowTimers, WindowSessionStorage, WindowLocalStorage, WindowConsole, GlobalEventHandlers, IDBEnvironment, WindowBase64, GlobalFetch {
    // ... 
    addEventListener<K extends keyof WindowEventMap>(
        type: K,
        listener: (this: Window, ev: WindowEventMap[K]) => any,
        options?: boolean | AddEventListenerOptions
    ): void

    removeEventListener<K extends keyof WindowEventMap>(
        type: K, listener: (this: Window, ev: WindowEventMap[K]) => any,
        options?: boolean | EventListenerOptions
    ): void
}

NEW TOPICS

/ ニュー & アップデート

SEE ALSO

/ 似た記事を見る

JY CONTENTS UNIQUE BLOG

search-menu search-menu