用語に関する注意: TypeScript 1.5 では、用語が変更されたことに注意することが重要です。「内部モジュール」は現在「名前空間」です。「外部モジュール」は、ECMAScript 2015 の用語(すなわち
module X {
が現在推奨されるnamespace X {
と同等である)に合わせて、単に「モジュール」と呼ばれるようになりました。
この記事では、TypeScript で名前空間 (以前は「内部モジュール」) を使用してコードを整理するさまざまな方法について概説します。用語に関する注意で述べたように、「内部モジュール」は現在「名前空間」と呼ばれています。また、内部モジュールを宣言するときに module
キーワードが使用されていた場所では、代わりに namespace
キーワードを使用できます (また、そうすべきです)。これにより、同様の名前の用語を過負荷にして新しいユーザーを混乱させることを回避できます。
最初のステップ
このページ全体で例として使用するプログラムから始めましょう。Webページ上のフォームでユーザーの入力を確認したり、外部から提供されたデータファイルの形式を確認したりするために記述するような、単純な文字列バリデーターの小さなセットを記述しました。
単一ファイル内のバリデーター
ts
interface StringValidator {isAcceptable(s: string): boolean;}let lettersRegexp = /^[A-Za-z]+$/;let numberRegexp = /^[0-9]+$/;class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: StringValidator } = {};validators["ZIP code"] = new ZipCodeValidator();validators["Letters only"] = new LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {let isMatch = validators[name].isAcceptable(s);console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);}}
名前空間
バリデーターを追加するにつれて、型を追跡し、他のオブジェクトとの名前の衝突を心配する必要がないように、何らかの組織化スキームが必要になります。グローバル名前空間に異なる名前をたくさん入れる代わりに、オブジェクトを名前空間にまとめましょう。
この例では、すべてのバリデーター関連エンティティを Validation
という名前の名前空間に移動します。ここにあるインターフェースとクラスを名前空間外から参照できるようにしたいので、それらに export
を付けます。逆に、変数 lettersRegexp
と numberRegexp
は実装の詳細なので、エクスポートせずに、名前空間外のコードからは見えなくします。ファイルの末尾にあるテストコードでは、名前空間外で使用する場合に型名を修飾する必要があります(例:Validation.LettersOnlyValidator
)。
名前空間化されたバリデーター
ts
namespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}const lettersRegexp = /^[A-Za-z]+$/;const numberRegexp = /^[0-9]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}export class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}}// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: Validation.StringValidator } = {};validators["ZIP code"] = new Validation.ZipCodeValidator();validators["Letters only"] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);}}
ファイル間分割
アプリケーションが成長するにつれて、保守を容易にするためにコードを複数のファイルに分割する必要があります。
マルチファイル名前空間
ここでは、Validation
名前空間を複数のファイルに分割します。ファイルは別々ですが、それぞれが同じ名前空間に貢献でき、すべてが1か所で定義されているかのように使用できます。ファイル間に依存関係があるため、ファイル間の関係をコンパイラーに伝えるために参照タグを追加します。それ以外の場合、テストコードは変更されていません。
Validation.ts
ts
namespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}}
LettersOnlyValidator.ts
ts
/// <reference path="Validation.ts" />namespace Validation {const lettersRegexp = /^[A-Za-z]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}}
ZipCodeValidator.ts
ts
/// <reference path="Validation.ts" />namespace Validation {const numberRegexp = /^[0-9]+$/;export class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);}}}
Test.ts
ts
/// <reference path="Validation.ts" />/// <reference path="LettersOnlyValidator.ts" />/// <reference path="ZipCodeValidator.ts" />// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: Validation.StringValidator } = {};validators["ZIP code"] = new Validation.ZipCodeValidator();validators["Letters only"] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {for (let name in validators) {console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`);}}
複数のファイルが関係する場合、コンパイルされたコードがすべてロードされるようにする必要があります。これには2つの方法があります。
まず、outFile
オプションを使用して、すべての入力ファイルを単一のJavaScript出力ファイルにコンパイルすることで、連結された出力を使用できます。
tsc --outFile sample.js Test.ts
コンパイラは、ファイルに存在する参照タグに基づいて、出力ファイルの順序を自動的に決定します。各ファイルを個別に指定することもできます。
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
または、ファイルごとのコンパイル(デフォルト)を使用して、入力ファイルごとに1つのJavaScriptファイルを出力できます。複数のJSファイルが生成される場合は、ウェブページで<script>
タグを使用して、出力された各ファイルを適切な順序でロードする必要があります。たとえば、次のようになります。
MyTestPage.html (抜粋)
html
<script src="Validation.js" type="text/javascript" /><script src="LettersOnlyValidator.js" type="text/javascript" /><script src="ZipCodeValidator.js" type="text/javascript" /><script src="Test.js" type="text/javascript" />
エイリアス
名前空間の操作を簡略化するもう1つの方法は、import q = x.y.z
を使用して、よく使用されるオブジェクトの短い名前を作成することです。モジュールをロードするために使用されるimport x = require("name")
構文と混同しないでください。この構文は、指定されたシンボルのエイリアスを作成するだけです。モジュールインポートから作成されたオブジェクトを含め、あらゆる種類の識別子に対して、このようなインポート(一般にエイリアスと呼ばれます)を使用できます。
ts
namespace Shapes {export namespace Polygons {export class Triangle {}export class Square {}}}import polygons = Shapes.Polygons;let sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'
require
キーワードを使用していないことに注意してください。代わりに、インポートするシンボルの完全修飾名から直接割り当てています。これはvar
の使用に似ていますが、インポートされたシンボルの型と名前空間の意味でも機能します。重要なのは、値の場合、import
は元のシンボルとは異なる参照であるため、エイリアス化されたvar
への変更は元の変数には反映されません。
他のJavaScriptライブラリとの連携
TypeScriptで記述されていないライブラリの形状を記述するには、ライブラリが公開するAPIを宣言する必要があります。ほとんどのJavaScriptライブラリはいくつかのトップレベルオブジェクトのみを公開するため、名前空間はそれらを表現するのに適した方法です。
実装を定義しない宣言を「環境」と呼びます。通常、これらは.d.ts
ファイルで定義されます。C/C++に詳しい場合は、これらを.h
ファイルと考えることができます。いくつかの例を見てみましょう。
環境名前空間
人気のライブラリD3は、その機能をd3
というグローバルオブジェクトで定義しています。このライブラリは(モジュールローダーではなく)<script>
タグを介してロードされるため、その宣言では名前空間を使用してその形状を定義します。TypeScriptコンパイラがこの形状を認識できるように、環境名前空間宣言を使用します。たとえば、次のように書き始めることができます。
D3.d.ts(簡略化された抜粋)
ts
declare namespace D3 {export interface Selectors {select: {(selector: string): Selection;(element: EventTarget): Selection;};}export interface Event {x: number;y: number;}export interface Base extends Selectors {event: Event;}}declare var d3: D3.Base;