TypeScriptで型定義を参照する方法!triple-slash directives(三斜線ディレクティブ)を徹底解説
生徒
「TypeScriptで、別のファイルにある型を使いたいのですが、うまく読み込めないことがあります。import以外に方法があるんですか?」
先生
「基本的にはimportを使いますが、特別な設定や古いライブラリを使うときには、reference directive(リファレンス・ディレクティブ)という方法を使います。」
生徒
「リファレンス・ディレクティブ...。名前からして難しそうですが、初心者でも使えますか?」
先生
「大丈夫ですよ!書き方の決まりさえ覚えれば簡単です。型定義ファイル(DefinitelyTyped)を扱う上でも大切な知識なので、一緒に見ていきましょう!」
1. TypeScriptの型定義の参照とは?
TypeScriptは、JavaScriptという言葉に「型(かた)」というルールを追加したプログラミング言語です。この「型」があるおかげで、私たちはプログラムを書いている最中に、間違いをすぐに発見することができます。しかし、プログラムが大きくなってくると、自分ですべての型を書くのは大変です。そこで、他の人が作った便利なプログラムの型情報を再利用する必要が出てきます。
通常、他のファイルにある機能を使いたいときはimportという命令を使いますが、型に関する情報だけを特別に読み込みたい場合や、システム全体の設定として特定の型を認識させたい場合に、reference directive(リファレンス・ディレクティブ)という特別な書き方を使います。これは、ファイルの一番上に特定の形式でコメントを書くことで、TypeScriptのコンパイラ(プログラムを翻訳する機械)に対して「この型情報も一緒に読み込んでね!」とお願いする機能です。
2. reference directiveの基本の書き方
リファレンス・ディレクティブは、別名「三斜線ディレクティブ(triple-slash directives)」とも呼ばれます。その名の通り、スラッシュを三つ重ねた///から始まる特殊なコメント形式です。パソコンを触ったことがない方にとって、コメントは「プログラムとしては動かないメモ」というイメージかもしれませんが、この三斜線で始まるものだけは、TypeScriptにとって特別な意味を持ちます。
もっとも基本的な書き方は、pathという属性を使って、読み込みたいファイルの場所を指定する方法です。例えば、同じフォルダにある「mytypes.d.ts」というファイルを読み込みたいときは、以下のように記述します。
/// <reference path="mytypes.d.ts" />
let userRole: MyUserType = "admin";
このように、ファイルの先頭に書くのが鉄則です。この一行があることで、下のコードで使っているMyUserTypeという型がどこで定義されているのかを、TypeScriptが見つけられるようになります。ファイルの場所を指定する際は、自分が今いる場所からの相対的な位置を書くことになります。例えば「子フォルダの中」ならpath="./types/sub.d.ts"のようになります。
3. DefinitelyTypedと型定義ファイル(.d.ts)の関係
TypeScriptを学んでいると、DefinitelyTyped(デフィニトリー・タイプド)という言葉をよく耳にします。これは、世界中のプログラマーが協力して作った「JavaScriptライブラリのための型定義集」のことです。JavaScriptで作られた古いライブラリには、型の情報が含まれていないことがよくあります。それではTypeScriptで不便なので、後付けで「このライブラリにはこんな型があるよ」と説明したファイルが作られました。それが型定義ファイル(拡張子が.d.tsのファイル)です。
これらの型定義ファイルを参照する際にも、リファレンス・ディレクティブが活躍することがあります。特に、@types/nodeや@types/jqueryといった、インストールしたパッケージの型を明示的に指定したい場合には、typesという属性を使います。これは、ファイルパスを直接書くのではなく、インストールされたパッケージの名前で指定する方法です。
/// <reference types="node" />
// Node.js固有の機能(processなど)の型が有効になります
console.log(process.version);
このコードを書くことで、プログラムはNode.jsという環境で動くことを前提とした型情報を手に入れることができます。これを手動で書かなくても動く設定もありますが、複雑なプロジェクトでは明示的に書くことでエラーを防ぐことができます。
4. なぜimportではなくreferenceを使うのか?
現代のTypeScript開発では、ほとんどのケースでimportを使います。では、なぜわざわざ古いようにも見えるリファレンス・ディレクティブを学ぶ必要があるのでしょうか。それにはいくつか理由があります。一つは、グローバルな型定義を読み込むためです。グローバルとは「どこからでも見える」という意味で、プログラムのどこでも共通して使いたいルールがある場合、リファレンス・ディレクティブが適しています。
もう一つの理由は、型定義ファイルそのもの(.d.tsファイル)を自作している場合です。複数の型定義ファイルがお互いに依存し合っているとき、それらのつながりを示すためにこの三斜線が使われます。例えば、料理のレシピ本(型定義ファイル)が、別の材料図鑑(別の型定義ファイル)を参照しているような状態をイメージしてください。この「本の索引」のような役割を果たすのがリファレンス・ディレクティブなのです。初心者の方は、まず「ライブラリの型が読み込めないときに使うことがあるおまじない」と覚えておけば十分です。
5. reference typesを使ったパッケージの参照例
実際に、有名なライブラリの型を参照する例を見てみましょう。ここでは例として、ウェブサイトに動きをつけるために昔からよく使われている「jQuery」という道具の型を読み込む場面を想定します。もし、自動で型が認識されない場合、以下のようにファイルの冒頭に記述します。
/// <reference types="jquery" />
// jQueryの記号「$」が型エラーにならずに使えるようになります
$(".main-button").on("click", function() {
alert("ボタンが押されました!");
});
この記述がないと、TypeScriptは「$って何ですか?定義されていませんよ!」と怒ってしまいます。しかし、リファレンス・ディレクティブでtypes="jquery"と指定してあげることで、「これはjQueryというライブラリの型を使っているんだな」と納得してくれるのです。このように、外部の道具(ライブラリ)と仲良くなるための橋渡し役をしてくれるのが、この機能の素晴らしいところです。
6. リファレンス・ディレクティブ使用時の注意点
リファレンス・ディレクティブを使う際には、いくつかの大切なルールがあります。これを守らないと、せっかく書いたのにお願いを聞いてもらえません。まず、必ずファイルの最上部に記述することです。たとえ一行でも、その前に普通のプログラムコードや、import文を書いてはいけません(コメントであれば前にあっても大丈夫な場合がありますが、基本は一番上です)。
また、スペルミスにも注意が必要です。referenceという単語の綴りや、pathやtypesといった属性の名前を間違えると、TypeScriptは無視してしまいます。XMLという形式に似た書き方なので、最後を/>で閉じることも忘れないようにしましょう。初心者の方がよくやってしまうミスは、普通のコメント//と間違えて、スラッシュを二つしか書かないことです。必ず三つ///書くことを意識してください。これはプログラムの世界では「ドキュメンテーション・コメント」の一種として扱われ、コンパイラへの命令になります。
7. 複数の型定義ファイルを連結して参照する
大規模な開発になると、型定義ファイルが一つに収まらず、役割ごとに分割されることがあります。例えば「ユーザーの型」「商品の型」「注文の型」が別々のファイルになっているような場合です。これらをまとめて管理するために、一つの「まとめ役ファイル」を作り、そこでリファレンス・ディレクティブを使って各ファイルを繋ぎ合わせることができます。
/// <reference path="user.d.ts" />
/// <reference path="product.d.ts" />
/// <reference path="order.d.ts" />
// これで、このファイルを使う人は一度にすべての型を参照できます
function createOrder(u: User, p: Product): Order {
return { user: u, item: p, date: new Date() };
}
このように書くことで、バラバラだった部品が一つの設計図として完成します。これを「型定義の依存関係の解決」と呼びます。難しい言葉ですが、要するに「このファイルを使うなら、あっちのファイルも必要だよ」という関係性をはっきりさせておく作業のことです。これによって、チームで開発するときも「あの型が見つからない!」というトラブルを防ぐことができます。
8. 実行結果を確認してみよう
リファレンス・ディレクティブが正しく機能しているかどうかは、プログラムを動かす前、つまりエディタ(VSCodeなど)でコードを書いている最中にわかります。正しく参照できていれば、未知の変数名の下に赤い波線(エラー)が出なくなります。例えば、特定のライブラリを読み込んだ後の動作をシミュレーションしてみましょう。
// 正しく参照できている場合のメッセージ例(コンパイル時)
Files: 5
Lines: 1200
Nodes: 4500
Identifiers: 120
Symbols: 800
Types: 300
Instantiations: 50
Memory used: 45M
I/O read: 0.01s
I/O write: 0.00s
Parse time: 0.12s
Bind time: 0.05s
Check time: 0.20s
Emit time: 0.02s
Total time: 0.39s
もし、リファレンス・ディレクティブの記述が間違っていて、型が見つからない場合は、以下のようなエラー画面が出てしまいます。これが出たときは、ファイルの場所(パス)が合っているか、パッケージ名が正しいかをもう一度確認してみましょう。特に初心者の方は、ファイルの拡張子(.d.ts)まで含めて正しく書けているかをチェックするのがコツです。
error TS2304: Cannot find name 'MyCustomType'.
error TS2688: Cannot find type definition file for 'wrong-package-name'.
9. コンパイラ設定ファイル(tsconfig.json)との使い分け
最近のTypeScriptでは、リファレンス・ディレクティブを個別のファイルに書く代わりに、tsconfig.jsonという設定ファイルにまとめて型定義の場所を書くことが推奨される場合が多いです。これは、クラス全員のルールを黒板に一括で書く(tsconfig.json)のと、個人のノートにそれぞれルールを書く(reference directive)の違いに似ています。
しかし、特定のファイルだけで特別な型を使いたい場合や、ライブラリの内部的な構造を作る場合には、今でもリファレンス・ディレクティブは必須の技術です。設定ファイルですべてを管理しようとすると、逆に設定が複雑になりすぎて混乱することもあります。状況に応じて「ここは一括設定で」「ここは個別にリファレンスで」と使い分けられるようになると、脱・初心者といえるでしょう。まずは、自分が使いたい型がどこにあるのかを意識することから始めてみてください。型を正しく参照できるようになれば、TypeScriptでの開発がぐっと楽しく、快適になりますよ!