TypeScript Handbook を読み進めていく第十五回目。
- Basic Types
- Variable Declarations
- Interfaces
- Classes
- Functions
- Generics
- Enums
- Type Inference
- Type Compatibility
- Advanced Types
- Symbols
- Iterators and Generators
- Modules
- Namwspaces
- Namespaces and Modules (今ココ)
- Module Resolution
- Declaration Merging
- JSX
- Decorators
- Mixins
- Triple-Slash Directives
- Type Checking JavaScript Files
Namespaces and Modules
Using Namespaces
名前空間は単なる名前付きオブジェクトであり、複数ファイルにまたがる名前空間を --outFile
で連結することが可能です。
名前空間は Web アプリーションを構築する際に <script>
を使用して依存ファイルを読み込む場合に使用すると良いでしょう。
ただし、特に大規模なアプリケーションにおいて、コンポーネント間の依存関係を識別することは難しくなります。
Using Modules
モジュールは名前空間と同じように実装と宣言を含んでいますが、モジュールはそれ自身の依存関係を 宣言 している点が異なります。
このことは小規模なアプリケーションでは必ずしも最適とは限りませんが、アプリケーションが大規模であれば、長期間にわたるモジュール性やメンテナンス性において、充分コストに見合った利益が得られます。
モジュールは ECMAScript 2015 から言語規格にも組み込まれており、新しいプロジェクトではコードを体系化するための手法として推奨します。
Pitfalls of Namespaces and Modules
/// <reference>
-ing a module
よくある間違いのひとつとして、モジュールファイルを参照するために import
構文ではなく、/// <reference ... />
を使用してしまうことが挙げられます。
なぜこれが間違いなのか理解するために、まずコンパイラがどのようにインポートパス (例: import x from "...";
や import x = require("...");
の ...
部分) に基づいてモジュールの型情報を読み込むか見てみましょう。
コンパイラは指定されたパスに対し、.ts
、.tsx
ファイルが存在するか確認した上で、適切なパスに .d.ts
ファイルが存在するか確認します。
もしも該当するファイルが存在しなかった場合、アンビエントモジュール宣言 が検索されます。
// .d.ts ファイル、またはモジュールではない .ts ファイルでの宣言
declare module "SomeModule" {
export function fn(): string;
}
/// <reference path="myModules.d.ts" />
import * as m from "SomeModule";
参照タグを使用することで、アンビエントモジュールを宣言しているファイルを指定することが可能です。
SomeModule.ts
、SomeModule.d.ts
を探しに行ってしまうため、参照タグで正しいパスを指定してるということかな?
Needless Namespacing
名前空間からモジュールを使用するようにプログラムを変更する場合、次のようなコードを書いてしまうかもしれません。
export namespace Shapes {
export class Triangle { /* ... */ }
export class Square { /* ... */ }
}
Shapes
で Triangle
、Square
をラップしていますが、これは何の意味もない上に、利用者の混乱を招いてしまいます。
import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle(); // shapes.Shapes だって?
モジュールの利用者は読み込んだモジュールの名前を指定することが可能なため、エクスポートするシンボルをわざわざ名前空間でラップする必要はありません。
モジュールにおいて名前空間を使用するべきでない理由を繰り返すと、名前空間は名前の衝突を避けるために用いられるものですが、そもそもモジュールはインポート時に名前を指定するため、さらにレイヤーを増やす必要性はまったくないということです。
それを踏まえた正しいコードは以下のとおりです。
export class Triangle { /* ... */ }
export class Square { /* ... */ }
import * as shapes from "./shapes";
let t = new shapes.Triangle();
Trade-offs of Modules
JS ファイルとモジュールに 1 対 1 の関係があるように、TypeScript でもモジュールファイルと出力される JS ファイルに 1 対 1 の関係があります。
つまり、使用するモジュールローダーによっては、複数のモジュールファイルを連結できないということです。
例えば、commonjs
や umd
を使用する場合には outFile
オプションを使用することはできませんが、TypeScript 1.8 以降であれば amd
や system
を使用することで outFile
オプションを使用できます。
まあ実際には webpack とかで連結するからいいんですけどね