エンターテイメント!!

遊戯王好きのJavaエンジニアのブログ。バーニングソウルを会得する特訓中。

TypescriptでのEmitterの付き合い方

きっかけ

TypeScriptをやるようになって、EventEmitterでどうも引っかかりを覚えた。
どうするべきか悩んだ挙句、やっと答えっぽいものが見えてきたので、まとめる。

TypescriptでのEmitterの付き合い方

環境

まずは、環境情報

> npm -v
4.0.5

> ver
Microsoft Windows [Version 10.0.15063]

ちなみに、エディターは、VisualStudioCode使ってます。
コードの参照先の検索や、定義に飛ぶのが他のエディターより楽だったので、コイツに落ち着きました。
できれば、リファクタリング機能が増えてくれると嬉しい。

Visual Studio Code - Visual Studio

package.json

{
  "name": "emitter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^7.0.13"
  }
}

tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6",
        "noImplicitAny": false,
        "sourceMap": false
    }
}

本題

よくある紹介記事

下記の内容の紹介をよく見かけると思う。

import { EventEmitter } from "events"

class EmitSample extends EventEmitter {

    constructor() {
        super();
        this.on("onTest", () => {
            console.log("on event");
        })
    }
}

const es = new EmitSample();
es.emit("onTest");

この例では、EventEmitterを継承して、コンストラクタで"onTest"イベントを監視する。
インスタンス作成後、emitでイベントを発火させている。
当然、コンパイルして実行すると下記のようになる。

> tsc EmitSample.ts
> node EmitSample.js
on event

しかし、これだと、開発していくうえでかなり問題がある。
それは、どこからでもemitできる点。
これを許していると、イベントを消したいときなんかは、grepをかけて検索する必要があります。
あと、許容される範囲がデカすぎて、Typescriptで想定される大規模開発をしていると、すぐにカオス化する。
いわゆるスパゲッティコードが簡単に出来上がる。
コンパイルで気づけないの点も非常に厄介です。
Typescript使っているから、コンパイルで気づけるって思っていても、コンパイルエラーにならないので、あとで爆発する。
今まで積み上げてきた努力の時間が無に帰る。
キラークイーンのバイツァ・ダストぐらいの威力があるに違いない。

対策としては、公開範囲を極小化すること。
具体的には、下記のようにする。

import { EventEmitter } from "events"

class EmitSample {

    private emitter: EventEmitter;

    constructor() {
        this.emitter = new EventEmitter();
        this.emitter.on("onTest", () => {
            console.log("on event");
        })
    }

    onTest() {
        this.emitter.emit("onTest");
    }
}

const es = new EmitSample();
es.onTest();

継承しなくなったことで、直接emitが呼び出せなくなる。
また、こうすることで、インスタンスに対する操作を提供する形になり、呼ばれたらどうなるのか明確化して、ある程度コントロールができるようになる。
不正なイベント呼び出しもできなくなるので安全に開発ができるようになる。
イベントが不要になったら、関数を削除するので、コンパイルでキチンと弾かれる。

これは、EventEmitterでよく再現されるObserverパターンでも導入可能。
なるべく公開範囲が小さくなるように作るのがコツだと、最近感じ始めた。

以上、終了。
最近は、Typescriptでオーバーロード使うべきか悩んでいる。
あと、ジェネリックスの適用方法も。
もう少し考えがまとまったら投稿予定。

TypeScript関連記事

suzaku-tec.hatenadiary.jp

suzaku-tec.hatenadiary.jp

suzaku-tec.hatenadiary.jp

suzaku-tec.hatenadiary.jp

suzaku-tec.hatenadiary.jp