TypeScriptのDeclaration Files(.d.ts)をマスターして、あらゆるJavaScriptライブラリの型安全と自動補完を実現しましょう。@typesの使用、独自の定義の作成、サードパーティコードのプロのように扱う方法を学びます。
JavaScriptエコシステムの解放:TypeScript Declaration Filesの詳細な解説
TypeScriptは、JavaScriptのダイナミックな世界に静的型付けをもたらすことで、最新のウェブ開発に革命をもたらしました。この型安全は、コンパイル時のエラー検出、強力なエディタの自動補完、大規模なコードベースのメンテナンス性の向上など、信じられないほどのメリットをもたらします。しかし、既存のJavaScriptライブラリの広大なエコシステムを使用したい場合に、大きな課題が生じます。それらのほとんどはTypeScriptで記述されていません。厳密に型付けされたTypeScriptコードは、型付けされていないJavaScriptライブラリの形状、関数、変数をどのように理解するのでしょうか?
その答えは、TypeScript Declaration Filesにあります。.d.ts拡張子で識別できるこれらのファイルは、TypeScriptとJavaScriptの世界を結ぶ不可欠な架け橋です。これらは、実際の実装を含まずに、サードパーティライブラリの型を記述するブループリントまたはAPIコントラクトとして機能します。この包括的なガイドでは、TypeScriptプロジェクトでJavaScriptライブラリの型定義を自信を持って管理するために知っておくべきことをすべて説明します。
TypeScript Declaration Filesとは?
あなたが、異なる言語しか話せない請負業者を雇ったと想像してください。彼らと効果的に仕事をするには、翻訳者か、あなたと彼らの両方が理解できる言語で詳細な指示書が必要です。declaration fileは、TypeScriptコンパイラ(請負業者)に対してこれとまったく同じ役割を果たします。
.d.tsファイルには、型情報のみが含まれています。これには次のものが含まれます。
- 関数とメソッドのシグネチャ(パラメータ型、戻り型)。
- 変数とその型の定義。
- 複雑なオブジェクトのインターフェースと型エイリアス。
- プロパティとメソッドを含むクラス定義。
- 名前空間とモジュールの構造。
重要なこととして、これらのファイルには実行可能コードは含まれていません。これらは純粋に静的分析用です。LodashのようなJavaScriptライブラリをTypeScriptプロジェクトにインポートすると、コンパイラは対応するdeclaration fileを探します。見つかった場合、コードを検証し、インテリジェントな自動補完を提供し、ライブラリを正しく使用していることを確認できます。見つからない場合は、次のようなエラーが発生します:モジュール 'lodash' のdeclaration fileが見つかりません。
Declaration Filesがプロの開発に不可欠な理由
TypeScriptプロジェクトで適切な型定義なしにJavaScriptライブラリを使用すると、そもそもTypeScriptを使用する理由が損なわれます。一般的なユーティリティライブラリであるLodashを使用した簡単なシナリオを考えてみましょう。
型定義のない世界
declaration fileがない場合、TypeScriptはlodashが何であるか、または何が含まれているかを知りません。コードをコンパイルするために、次のような簡単な修正を使用するように誘われるかもしれません。
const _: any = require('lodash');
const users = [{ 'user': 'barney' }, { 'user': 'fred' }];
// 自動補完?ここでは助けにならない。
// 型チェック?いいえ。「username」は正しいプロパティですか?
// コンパイラはこれを許可しますが、実行時に失敗する可能性があります。
_.find(users, { username: 'fred' });
この場合、_変数はany型です。これは事実上、TypeScriptに「この変数に関連するものは何もチェックしないでください」と伝えます。自動補完、引数の型チェック、戻り型に関する確実性など、すべてのメリットを失います。これは実行時エラーの温床です。
型定義のある世界
次に、必要なdeclaration fileを提供した場合に何が起こるかを見てみましょう。(次に説明する)型をインストールすると、エクスペリエンスが変わります。
import _ from 'lodash';
interface User {
user: string;
active?: boolean;
}
const users: User[] = [{ 'user': 'barney' }, { 'user': 'fred' }];
// 1. エディタは「find」および他のlodash関数の自動補完を提供します。
// 2. 「find」にカーソルを合わせると、完全なシグネチャとドキュメントが表示されます。
// 3. TypeScriptは、`users`が`User`オブジェクトの配列であることを認識します。
// 4. TypeScriptは、`User[]`の`find`の述語が`user`または`active`を含む必要があることを認識しています。
// 正しい:TypeScriptは満足しています。
const fred = _.find(users, { user: 'fred' });
// エラー:TypeScriptが間違いを検出します!
// プロパティ 'username' は型 'User' に存在しません。
const betty = _.find(users, { username: 'betty' });
その違いは昼と夜ほどです。完全な型安全、ツールによる優れた開発者エクスペリエンス、潜在的なバグの劇的な削減が得られます。これは、TypeScriptを使用する際のプロフェッショナルな標準です。
型定義を見つけるための階層
では、お気に入りのライブラリのこれらの魔法のような.d.tsファイルはどのように入手するのでしょうか?ほとんどのシナリオをカバーする確立されたプロセスがあります。
ステップ1:ライブラリが独自の型をバンドルしているかどうかを確認する
最も良いシナリオは、ライブラリがTypeScriptで記述されているか、メンテナが同じパッケージ内で公式のdeclaration fileを提供している場合です。これは、最新の、適切にメンテナンスされているプロジェクトでますます一般的になっています。
確認方法:
- 通常どおりライブラリをインストールします:
npm install axios node_modules/axiosのライブラリのフォルダ内を見てください。.d.tsファイルはありますか?- ライブラリの
package.jsonファイルで"types"または"typings"フィールドを確認します。このフィールドは、メインのdeclaration fileを直接指します。たとえば、Axiosのpackage.jsonには、"types": "index.d.ts"が含まれています。
これらの条件が満たされている場合、完了です!TypeScriptはこれらのバンドルされた型を自動的に見つけて使用します。これ以上の操作は必要ありません。
ステップ2:DefinitelyTypedプロジェクト(@types)
独自の型をバンドルしない何千ものJavaScriptライブラリのために、グローバルなTypeScriptコミュニティは信じられないほどの資源を作成しました:DefinitelyTyped。
DefinitelyTypedは、GitHub上の一元化された、コミュニティ管理のリポジトリであり、多数のJavaScriptパッケージの高品質なdeclaration fileをホストしています。これらの定義は、@typesスコープでnpmレジストリに公開されます。
使い方:
lodashのようなライブラリが独自の型をバンドルしていない場合は、対応する@typesパッケージを開発依存関係としてインストールするだけです。
npm install --save-dev @types/lodash
命名規則は単純で予測可能です:package-nameという名前のパッケージの場合、その型はほとんどの場合@types/package-nameにあります。npm WebサイトまたはDefinitelyTypedリポジトリで直接利用可能な型を検索できます。
なぜ--save-dev?declaration fileは、開発およびコンパイル中にのみ必要です。ランタイムコードは含まれていないため、最終的な本番バンドルに含めるべきではありません。これらをdevDependenciesとしてインストールすることで、この分離が保証されます。
ステップ3:型が存在しない場合 - 自分で作成する
型をバンドルせず、DefinitelyTypedにもない、古い、ニッチな、または内部プライベートライブラリを使用している場合はどうでしょうか?この場合、袖をまくり上げて、独自のdeclaration fileを作成する必要があります。これは気が遠くなるように聞こえるかもしれませんが、簡単に始めて、必要に応じて詳細を追加できます。
クイックフィックス:省略形のアンビエントモジュール宣言
適切な型付け戦略を理解している間、エラーなしでプロジェクトをコンパイルする必要がある場合があります。プロジェクトにファイル(例:declarations.d.tsまたはtypes/global.d.ts)を作成し、省略形の宣言を追加できます。
// .d.tsファイル内
declare module 'some-untyped-library';
これはTypeScriptに、「'some-untyped-library'という名前のモジュールが存在することを信頼してください。そこからインポートされたものはすべてany型として扱ってください」と伝えます。これにより、コンパイラエラーは抑制されますが、すでに説明したように、そのライブラリの型安全はすべて犠牲になります。これは一時的なパッチであり、長期的な解決策ではありません。
基本的なカスタムDeclaration Fileの作成
より良いアプローチは、実際に使用するライブラリの部分の型を定義することから始めることです。`string-utils`という名前の単一の関数をエクスポートする単純なライブラリがあるとしましょう。
// node_modules/string-utils/index.js内
module.exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
プロジェクトのルートにある専用の`types`ディレクトリにstring-utils.d.tsファイルを作成できます。
// my-project/types/string-utils.d.ts内
declare module 'string-utils' {
export function capitalize(str: string): string;
// 他の関数定義をここに追加できます
// export function slugify(str: string): string;
}
次に、TypeScriptにカスタム型定義の場所を伝える必要があります。これは、tsconfig.jsonで行います。
{
"compilerOptions": {
// ... その他のオプション
"baseUrl": ".",
"paths": {
"*": ["types/*"]
}
}
}
この設定で、import { capitalize } from 'string-utils'を使用すると、TypeScriptはカスタムdeclaration fileを見つけて、定義した型安全を提供します。ライブラリのより多くの機能を使用するにつれて、このファイルを徐々に構築できます。
さらに深く掘り下げる:Declaration Fileの作成
declaration fileを作成または読み取る際に遭遇する、より高度な概念をいくつか見てみましょう。
さまざまな種類のエクスポートの宣言
JavaScriptモジュールは、さまざまな方法でエクスポートできます。declaration fileは、ライブラリのエクスポート構造と一致する必要があります。
- 名前付きエクスポート:これは最も一般的です。上記で`export function capitalize(...)`で確認しました。定数、インターフェース、クラスもエクスポートできます。
- デフォルトエクスポート:`export default`を使用するライブラリの場合。
- UMDグローバル:
<script>タグを介してブラウザで動作するように設計された古いライブラリの場合、グローバルな`window`オブジェクトに自身をアタッチすることがよくあります。これらのグローバル変数を宣言できます。 - `export =` と `import = require()`:この構文は、`module.exports = ...`を使用する古いCommonJSモジュール用です。たとえば、ライブラリが`module.exports = myClass;`を実行する場合。
declare module 'my-lib' {
export const version: string;
export interface Options { retries: number; }
export function doSomething(options: Options): Promise
declare module 'my-default-lib' {
// 関数のデフォルトエクスポートの場合
export default function myCoolFunction(): void;
// オブジェクトのデフォルトエクスポートの場合
// const myLib = { name: 'lib', version: '1.0' };
// export default myLib;
}
// 特定の型のグローバル変数 '$' を宣言します
declare var $: JQueryStatic;
// my-class.d.ts内
declare class MyClass { constructor(name: string); }
export = MyClass;
// app.ts内
import MyClass = require('my-class');
const instance = new MyClass('test');
最新のESモジュールではあまり一般的ではありませんが、これは多くの古いが依然として広く使用されているNode.jsパッケージとの互換性にとって重要です。
モジュール拡張:既存の型の拡張
最も強力な機能の1つは、モジュール拡張(declaration mergingとも呼ばれます)です。これにより、別のパッケージのdeclaration fileで定義された既存のインターフェースにプロパティを追加できます。これは、ExpressやFastifyのようなプラグインアーキテクチャを備えたライブラリに非常に役立ちます。
Expressで、`Request`オブジェクトに`user`プロパティを追加するミドルウェアを使用していると想像してください。拡張がない場合、TypeScriptは`user`が`Request`に存在しないと文句を言います。
この新しいプロパティについてTypeScriptに伝える方法は次のとおりです。
// types/express.d.tsファイル内
// 拡張するには、元の型をインポートする必要があります
import { UserProfile } from './auth'; // UserProfile型があると仮定します
// 'express-serve-static-core'モジュールを拡張していることをTypeScriptに伝えます
declare module 'express-serve-static-core' {
// そのモジュール内の 'Request' インターフェースをターゲットにします
interface Request {
// カスタムプロパティを追加します
user?: UserProfile;
}
}
これで、アプリケーション全体で、Express `Request`オブジェクトはオプションの`user`プロパティで正しく型付けされ、完全な型安全と自動補完が得られます。
トリプルスラッシュディレクティブ
.d.tsファイルの先頭に3つのスラッシュ(///)で始まるコメントが表示されることがあります。これらはトリプルスラッシュディレクティブであり、コンパイラの指示として機能します。
/// <reference types="..." />:これは最も一般的なものです。別のパッケージの型定義を依存関係として明示的に含めます。たとえば、WebdriverIOプラグインの型には、/// <reference types="webdriverio" />が含まれている場合があります。これは、独自の型がコアWebdriverIO型に依存しているためです。/// <reference path="..." />:これは、同じプロジェクト内の別のファイルへの依存関係を宣言するために使用されます。これは古い構文であり、ESモジュールインポートにほぼ取って代わられています。
Declaration Filesを管理するためのベストプラクティス
- バンドルされた型を優先する:ライブラリを選択する際は、TypeScriptで記述されているか、独自の公式型定義をバンドルしているものを優先します。これは、TypeScriptエコシステムへのコミットメントを示しています。
devDependenciesに@typesを保持する:常に--save-devまたは-Dを使用して@typesパッケージをインストールしてください。これらは本番コードには必要ありません。- バージョンを揃える:一般的なエラーの原因は、ライブラリのバージョンと
@typesバージョンの不一致です。ライブラリのメジャーバージョンアップ(たとえば、v2からv3へ)では、APIに破壊的な変更が生じる可能性が高く、これは@typesパッケージに反映される必要があります。これらを同期させるようにしてください。 tsconfig.jsonを使用して制御する:tsconfig.jsonのtypeRootsおよびtypesコンパイラオプションを使用すると、TypeScriptがdeclaration fileを探す場所を細かく制御できます。typeRootsは、コンパイラがチェックするフォルダ(デフォルトでは./node_modules/@types)を指示し、typesを使用すると、含める型パッケージを明示的にリストできます。- 貢献する:型を持たないライブラリの包括的なdeclaration fileを作成する場合は、それをDefinitelyTypedプロジェクトに貢献することを検討してください。これは、グローバルな開発者コミュニティに貢献し、何千人もの他の人を助けるための素晴らしい方法です。
結論:型安全の縁の下の力持ち
TypeScript Declaration Filesは、JavaScriptのダイナミックで広大な世界を、堅牢で型安全な開発環境にシームレスに統合することを可能にする縁の下の力持ちです。これらは、ツールに力を与え、無数のバグを防ぎ、コードベースをより回復力があり、自己文書化するようにする重要なリンクです。
.d.tsファイルを見つけ、使用し、さらには作成する方法を理解することで、コンパイラエラーを修正するだけでなく、開発ワークフロー全体を向上させています。TypeScriptとJavaScriptライブラリの豊富なエコシステムの両方の可能性を最大限に引き出し、グローバルな聴衆のために、より優れた、より信頼性の高いソフトウェアを生み出す強力な相乗効果を生み出しています。