この投稿では、TypeScriptでモジュールと名前空間を使用してコードを整理するさまざまな方法について説明します。また、名前空間とモジュールの高度な使い方や、TypeScriptで使用する際の一般的な落とし穴についても説明します。
ESモジュールに関する詳細については、モジュールのドキュメントを参照してください。TypeScript名前空間に関する詳細については、名前空間のドキュメントを参照してください。
注: 非常に古いバージョンのTypeScriptでは、名前空間は「内部モジュール」と呼ばれていました。これらはJavaScriptモジュールシステムより前に存在していました。
モジュールの使用
モジュールにはコードと宣言の両方を含めることができます。
モジュールは、モジュールローダー(CommonJs/Require.jsなど)またはESモジュールをサポートするランタイムへの依存関係も持っています。モジュールは、より優れたコードの再利用、より強力な分離、バンドル向けのより優れたツールサポートを提供します。
また、Node.jsアプリケーションの場合、モジュールがデフォルトであり、現代のコードでは名前空間よりもモジュールを推奨することに注意してください。
ECMAScript 2015以降、モジュールは言語のネイティブな一部であり、すべての準拠したエンジン実装でサポートされる必要があります。したがって、新しいプロジェクトでは、モジュールが推奨されるコード構成メカニズムになります。
名前空間の使用
名前空間は、TypeScript固有のコードを整理する方法です。
名前空間は、グローバル名前空間内の単なる名前付きJavaScriptオブジェクトです。これにより、名前空間は非常にシンプルな構造になります。モジュールとは異なり、複数のファイルにまたがることができ、outFile
を使用して連結できます。名前空間は、Webアプリケーションでコードを構造化するのに適した方法であり、すべての依存関係がHTMLページに<script>
タグとして含まれています。
すべてのグローバル名前空間の汚染と同様に、特に大規模なアプリケーションでは、コンポーネントの依存関係を特定するのが難しい場合があります。
名前空間とモジュールの落とし穴
このセクションでは、名前空間とモジュールの使用における様々な一般的な落とし穴と、それらを回避する方法について説明します。
/// <reference>
を使用したモジュールの参照
よくある間違いは、import
ステートメントを使用する代わりに、/// <reference ... />
構文を使用してモジュールファイルを参照しようとすることです。この区別を理解するには、まず、コンパイラが import
のパス(例: import x from "...";
、import x = require("...");
などの ...
の部分)に基づいて、モジュールの型情報をどのように見つけられるかを理解する必要があります。
コンパイラは、適切なパスを持つ .ts
、.tsx
、そして .d.ts
を見つけようとします。特定のファイルが見つからなかった場合、コンパイラはアンビエントモジュール宣言を探します。これらは .d.ts
ファイルで宣言する必要があることを思い出してください。
-
myModules.d.ts
ts// In a .d.ts file or .ts file that is not a module:declare module "SomeModule" {export function fn(): string;} -
myOtherModule.ts
ts/// <reference path="myModules.d.ts" />import * as m from "SomeModule";
ここでの参照タグを使用すると、アンビエントモジュールの宣言を含む宣言ファイルを見つけることができます。これは、TypeScriptサンプルのいくつかが使用している node.d.ts
ファイルがどのように消費されるかを示すものです。
不必要な名前空間
名前空間からモジュールにプログラムを変換している場合、次のようなファイルになりがちです。
-
shapes.ts
tsexport namespace Shapes {export class Triangle {/* ... */}export class Square {/* ... */}}
ここでのトップレベルの名前空間 Shapes
は、理由もなく Triangle
と Square
をラップしています。これは、モジュールの利用者にとって混乱を招き、迷惑です。
-
shapeConsumer.ts
tsimport * as shapes from "./shapes";let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
TypeScriptにおけるモジュールの重要な機能は、2つの異なるモジュールが同じスコープに名前を提供しないことです。モジュールの利用者がどの名前を割り当てるかを決定するため、エクスポートされたシンボルを名前空間で積極的にラップする必要はありません。
モジュールのコンテンツを名前空間でラップしようとすべきではない理由を繰り返しますが、名前空間の一般的な考え方は、構成要素の論理的なグループ化を提供し、名前の衝突を防ぐことです。モジュールファイル自体がすでに論理的なグループ化であり、そのトップレベルの名前はそれをインポートするコードによって定義されるため、エクスポートされたオブジェクトに追加のモジュールレイヤーを使用する必要はありません。
改訂された例を以下に示します。
-
shapes.ts
tsexport class Triangle {/* ... */}export class Square {/* ... */} -
shapeConsumer.ts
tsimport * as shapes from "./shapes";let t = new shapes.Triangle();
モジュールのトレードオフ
JSファイルとモジュールに一対一の対応があるように、TypeScriptには、モジュールのソースファイルと、それらが出力するJSファイルとの間に一対一の対応があります。この影響の1つは、ターゲットとするモジュールシステムによっては、複数のモジュールソースファイルを連結することができないことです。たとえば、commonjs
または umd
をターゲットにしている間は、outFile
オプションを使用することはできませんが、TypeScript 1.8 以降では、amd
または system
をターゲットにする場合は outFile
を使用できます。