10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flowtype+Atom+Nuclideで安全にEventを扱う

Posted at

皆さんイベントを扱ってますか!!
JavaScriptではイベントは避けては通れない道ではありますが、イベントを送る、受け取るのには文字列でイベント名を指定してあげる必要があります。
このイベント名を指定するというのが曲者で、文字列での指定になるためどうしてもtypoが発生し、イベントの送受信が上手くいかない!!という事態になりやすかったりします。

さすがに目視でtypoチェックとかつらすぎるので、ここはFlowtypeを使って型の力でこの問題を解決してしまいます。

Flowtype

Flowtypeはstring literal typeと関数のoverloadに対応しています。

string literal type

var foo: "foo" = "bar"// -> Error!!

overload

declare class Foo {
  bar(a: string): void;
  bar(a: number, b: number): void;
}

というわけで、この2つの機能を使ってElectronのappのイベントを定義してみます。

flow/electron.js.flow
declare class ElectronApp {
    on(evt: 'will-finish-launching' | 'ready' | 'window-all-closed' | 'gpu-process-crashed' | 'platform-theme-changed', listener: () => void): EventEmitter;
    on(evt: 'before-quit' | 'will-quit', listener: (event: Event) => void): EventEmitter;
    on(evt: 'quit', listener: (event: Event, exitCode: number) => void): EventEmitter;
    on(evt: 'open-file', listener: (event: Event, path: string) => void): EventEmitter;
    on(evt: 'open-url', listener: (event: Event, url: string) => void): EventEmitter;
    on(evt: 'activate', listener: (event: Event) => void, hasVisibleWindows: boolean): EventEmitter;
    on(evt: 'browser-window-blur' | 'browser-window-focus' | 'browser-window-created', listener: (event: Event, window: BrowserWindow) => void): EventEmitter;
    on(evt: 'certificate-error' | 'select-client-certificate', listener: (event: Event, webContents: WebContents, url: string, error: string, certificate: {data: Buffer, issuerName: string}, callback: Function) => void): EventEmitter;
    on(evt: 'login', listener: (event: Event, webContents: WebContents, request: {method: string, url: string, referrer: string}, authInfo: {isProxy: boolean, scheme: string, host: string, port: number, realm: string}, callback: Function) => void): EventEmitter;
}

declare module electron {
  declare var app: ElectronApp;
}

この定義を使用すると

import {app} from 'electron';

app.on('ready', () => { }); // no error
app.on('ready1', () => { }); // error

というように未定義のイベント名に対してエラーを発生させることができるようになります。
さらに、listenerの引数も定義しているので

import {app} from 'electron';

app.on('quit', (event: Event, exitCode: number) => {}); // no error
app.on('quit', (event: Event, exitCode: string) => {}); // error

というようにlistenerに渡す関数の型チェックも行えます。
これでうっかり別の関数をlistenerに渡してしまうなんていうミスもなくせるので素敵ですね!

Atom+Nuclide

ここまでしっかりと型定義をし始めると、開発効率を上げるためにコード補完しつつ、C++やJavaのように型のミスマッチをすぐに警告して欲しくなってきます。
いくつかFlowtype対応をうたってるIDEやエディタを試してみたところ

この組み合わせが一番自分にマッチしていました。(というか他のがFlowtypeの設定を書く.flowconfigに対応してないのばかりでコレ以外に選択肢がありませんでした)

Atom+NuclideをインストールしてFlowtypeのコードを書くとこのように軽快に補完しつつ、型チェックを行うことが出来るようになります。
これで手軽に、かつ安全にイベントを扱えるようになって最高ですね!!!

つらい話

最高ですねで済めば幸せだったのですが、まぁそんな上手い話はない訳で。

正直なところ型定義がつらいです。
d.tsではここまで型定義されていないので持ってきてFlowtypeの型定義に直すということはできませんし、ドキュメントにイベントで渡される引数がなかったり、実は違う引数があったりで、コードを読む必要が出てきたりで型定義がかなり面倒臭いです。

また、Flowtypeでは型定義と実際のコードが分かれて管理する必要があるため、型定義の保守を怠ると簡単に型と実際に動作するコードが乖離しだしてしまいます。
型安全が欲しくて型定義を書いてるのに、実際に動かすと違う型が渡されて型定義の意味がないというつらみがあります。
(Scala.jsみたいに実行時に型の検証すべきなのかなぁ)

何か上手い方法が欲しいです。

おわりに

少し前に

という記事でもイベントを扱いましたが、ライブラリが発火するイベントを扱うなら今回の方法がスマートです。(普通にイベントを書けば型が効きますし)
逆に自作のイベントを扱うならコードとイベントを乖離させずにイベントを定義できるPhantom Typeの方が使い易いパターンもありそうです。

このあたりで、こう書くとより安全に書けるよ!などあればぜひぜひ教えてください。

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?