Nodejsでイベント駆動プログラミングをするときに便利なのがevent.EventEmitterですが、これをTypeScriptで利用しようとするとうまく型安全にできません。
const event = new EventEmitter();
event.on('foo', arg => console.log(arg));
// argは必然的にanyとなる...
event.emit('foo', 123);
event.emit('foo', '123');
event.emit('foo', {}); // なんでもあり
この問題に対する解決策は以前からいくつか存在していましたが、どの方法もデメリットがある状態でした。
しかし、TypeScript 3.0から導入されたConditional Typesを利用することによりほぼ完全なイベントの型付けを実現できるようになりました。
今回は、それをライブラリとして利用しやすくしたstrict-event-emitter-typesを利用したので、忘備録として紹介します。
簡単な使用方法
大体README.mdに書かれている内容と同じです。
事前の準備として、イベントの型情報をまとめたインターフェースと、EventEmitterを取得します。
import StrictEventEmitter from 'strict-event-emitter-types';
// イベントの定義
interface Events {
request: (request: Request, response: Response) => void;
data: String; // (data: String) => void; のショートハンド
done: void; // 引数なし
}
import { EventEmitter } from 'events';
// イベントエミッタとイベント定義を元にStrictEventEmitterを作成
const ee: StrictEventEmitter = new EventEmitter();
この方法で生成されたEventEmitter(サンプル内ee)は、全て型安全で利用することができます。
利用方法は普段のEventEmitterと同様です。
ee.on('request', (req, res) => ... );
// reqはRequest, resはResponseとして認識される。
ee.on('something', () => ... );
// イベントが存在しないのでコンパイルエラー
ee.on('done', hoge => ... );
// doneには引数がないのでコンパイルエラー
ee.emit('data', 'こんにちは!');
// OK
ee.emit('data', 12345);
// 型が合わないのでコンパイルエラー
簡単ですね。
ただし、TypeScriptの動作により、イベント定義インターフェースに不具合があった場合に不明瞭なエラーメッセージが出現します。
これはTypeScript側で修正予定だそうです。
strict-event-emitter-typesをラップする方法について
プルリクエストを送ってある件ですが、元ソース中のListenerTypeがExportされていないため、このライブラリのラッパを作成することができません。
Export ListenerType by kznrluk · Pull Request #13 · bterlson/strict-event-emitter-types
ローカルで編集してしまうか、私のPR元ブランチをクローンして使ってください。
kznrluk/strict-event-emitter-types: A type-only library for strongly typing any event emitter