Try .NET Core

.NET Coreを動かした、試した記録を書き残します。

音楽サーバ"Mopidy"のフロントエンドを作る:09 TypeScript用の型定義をつくる、その2

音楽サーバ"Mopidy"のフロントエンド「Mopidy.Finder」が出来るまで、第9回です。

今回も引き続き、TypeScriptでライブラリを使う際の型定義について、です。

@typesにない、だと...

Mopidy.FinderではUIの画面サイズの判定に、BootstrapのためのユーティリティResponsive Bootstrap Toolkitを使っています。

それを@typeでsearchしてみると... f:id:try_dot_net_core:20190810161131p:plain

うへえ。見つかんねえ...
大正義@typesとはいえ、型定義が存在しないライブラリも、あるんですね。

ないなら書けばいいじゃない

そう、定義が無いなら、書けばいいのです。
ゴハンが無いならケーキを食えばいいのです。

めんどくせえよアントワネット!

あら、意外とそうでもありませんですのよ。オホホ。

使うとこだけ、書けばいい

この、Responsive Bootstrap Toolkit
欲しい機能は、

  • isメソッド: Windowサイズ比較用メソッド
  • changedコールバック: Windowサイズ変更時のコールバック
  • currentメソッド: 現在のWindowサイズを取得するメソッド

くらいなもんです。
加えて、このライブラリはまだBootstrap4に対応していないため、

  • useメソッド: Windowサイズ定義をセットするメソッド

も入れておきます。

別にDefinitelyTypedリポジトリにプルリクする訳でもなく、自分が使うだけのこと。
それなら、使うところだけを定義しておけばいいのです!

定義の種類

まず定義するに当たって、読み込むライブラリが何者なのかを調べます。
型定義にはおおむね、下の4種類が多く見受けられます。

  1. newして使う、クラスの定義
  2. 既に生成済みのインスタンスの定義
  3. 各種クラスやインタフェース類、staticメソッドを集めた、名前空間としての定義
  4. staticなメソッドを持ち、かつnewでインスタンスを生成するクラスでもある定義。

このうち1.のクラス定義は、クラスベースのTypeScriptでは最も馴染みやすいものです。
declare class [クラス名]としておき、中身はインタフェースを書く要領でpublicメンバーを書き連ねていきます。

4.の複合型定義は、見かける頻度は高いんですが、書くのはしんどいです。
クラスと名前空間を同じ名前で定義し、staticメソッドやインタフェース定義などを名前空間に集めておく書き方です。
前回記事に挙げたSortableJSの定義が、このように作られています。

そして、冒頭で挙げたResponsive Bootstrap Toolkitは、どんなものでしょう?
ライブラリの使い方としては、下記のように書いてます。

import * as ResponsiveBootstrapToolkit from 'responsive-toolkit/dist/bootstrap-toolkit';

if (ResponsiveBootstrapToolkit.is('<=md')) {
    // md以下のときの処理
}

これは、2.のインスタンスをimportしている、と考えてよいでしょう。

インスタンスの型定義

型定義ファイルで書くdeclare構文。
これは、そのライブラリをimportしたときに存在するはずのもの、を示しています。

クラスが存在するはずならば、declare class [クラス名]となります。
Responsive Bootstrap Toolkitの場合はクラスでなくインスタンスになるため、存在するのは生成済みインスタンスの入れ物になる、変数名です。

この場合は、const/varで宣言された変数をdeclareします。

declare const ResponsiveBootstrapToolkit;

そして、そのインスタンスが持つべきメンバーを、インタフェースとして定義します。

interface IResponsiveBootstrapToolkit {
    is(complareString: string): boolean;
    changed(callback: () => void, waitMsec?: number): void;
    current(): string;
    use(framewrokName: string, breadpoints: { [breadkpoint: string]: JQuery }): void;
}

declareしたインスタンスは上述のインタフェースを持つため、型を指定してやります。

declare const ResponsiveBootstrapToolkit: IResponsiveBootstrapToolkit;

最後に、このライブラリはモジュールのため、存在宣言した変数をexportしてやります。

export = ResponsiveBootstrapToolkit;

全部合わせて、8行のコードで済みました!
types/responsive-toolkit/index.d.ts:

interface IResponsiveBootstrapToolkit {
    is(complareString: string): boolean;
    changed(callback: () => void, waitMsec?: number): void;
    current(): string;
    use(framewrokName: string, breadpoints: { [breadkpoint: string]: JQuery }): void;
}
declare const ResponsiveBootstrapToolkit: IResponsiveBootstrapToolkit;
export = ResponsiveBootstrapToolkit;

これで、Responsive Bootstrap Toolkitが、型安全に使えるようになります!

名前空間、と解釈してみる

このResponsive Bootstrap Toolkit生成済みインスタンスとして扱っていますが。
これは、staticなメソッドを持つ名前空間、として見ることも出来ます。

そのように、書き換えてみたのがこちら。

declare namespace ResponsiveBootstrapToolkit {
    function is(complareString: string): boolean;
    function changed(callback: () => void, waitMsec?: number): void;
    function current(): string;
    function use(framewrokName: string, breadpoints: { [breadkpoint: string]: JQuery }): void;
}
export = ResponsiveBootstrapToolkit;

これでも、ビルドは通ります。

ただ、名前空間として考えるには、少し機能が小さすぎますかね?
そのへんは、お好みで。

以上、TypeScriptの型定義づくり、その2でした!