名前空間(namespace)

用語に関する注意:
TypeScript1.5において用語の変更における重要な注意事項があります。

ECMAScript 2015の用語に合わせ、現在では、 内部モジュール("Internal modules")は"namespaces"となり、 外部モジュール("External modules")は単純に"modules"となりました。 具体的には、module X {は、namespace X {と書くのが好ましいと言えます。

イントロダクション

ここでは、TypeScriptで名前空間(以前は"内部モジュール"と呼ばれていました)を使用してコードを構造化していく様々な方法について説明します。 用語の注意で触れたように、「内部モジュール」は現在では「名前空間」と呼ばれています。 加えて、いずれかの場所で内部モジュールの宣言でmoduleキーワードが使用されている場合、 代わりにnamespaceキーワードを使用することが可能であり、それを使用するべきです。 こうすることで、同じような名前の用語によって新しい開発者が混乱することを防ぎます。

初めの一歩

このページのサンプルを使用して、プログラムを始めていきましょう。 我々は、Webページのフォームでユーザーの入力を、 また外部から提供されるデータファイルのフォーマットをチェックする簡単な文字列検証(validator)の小さなまとまりを用意しました。

検証処理(単一ファイル)
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);
    }
}

// いくつかの例を試す
let strings = ["Hello", "98052", "101"];

// validatorを使用
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();

// 各文字列が各検証をパスしたかを表示
for (let s of strings) {
    for (let name in validators) {
        let isMatch = validators[name].isAcceptable(s);
        console.log(`'${ s }' ${ isMatch ? "matches" : "does not match" } '${ name }'.`);
    }
}

名前空間化

Validatorを追加する際に、型の追跡を保持しつつ、別のオブジェクトとの名前の衝突を心配する必要を無くすための、 ある種の組織化された構造が必要となることが考えられます。 多くの異なる名前をグローバル上に配置する代わりに、 オブジェクトを名前空間の中にラップしてしまいましょう。

この例では、全ての名前空間に関するエンティティをValidationという名前空間に移動します。 我々はここでインターフェースとクラスを名前空間の外から見えるようにしたいため、 これらの前にexportを指定します。 反対にlettersRegexpnumberRegexpは細部の実装であるため、 exportをしないままにすることで、名前空間の外側のコードから見えないようにします。 ファイル下部のテストコードでは、名前空間の外で使用する際には、 例えばValidation.LettersOnlyValidatorのようにして、型の名前を指定する必要があります。

名前空間が適用されたValidator
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);
        }
    }
}

// いくつかのサンプルを試す
let strings = ["Hello", "98052", "101"];

// Validatorを使用
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 各文字列が各検証をパスしたかを表示
for (let s of strings) {
    for (var name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}

ファイルの分割

アプリケーションの規模が大きくなると、メンテナンス性を保つためにコードを複数のファイルに分割したくなります。

複数ファイルの名前空間

ここでは、Validation名前空間を複数のファイルに分けます。 ファイルが分割されたとしても、それらは互いに同じ名前空間として提供しあい、 全てが同じ場所で提供されているかのように使用することが可能です。 これらは依存関係にあるため、リファレンスタグを追加して、コンパイラにファイル間の関連性について伝えます。 それ以外のテストコードの変更はありません。

Validation.ts
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
LettersOnlyValidator.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
/// <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
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// Show whether each string passed each validator
for (let s of strings) {
    for (let name in validators) {
        console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
    }
}

複数のファイルに分けるにあたり、コンパイルされたコードが全て読み込まれていることを保証する必要があります。 これを行うのに、2つの方法が存在します。

1つ目は--outFileフラグを使用して、全ての入力ファイルを連結して単一のFavaScriptファイルとして出力します。

tsc --outFile sample.js Test.ts

コンパイラはファイル内で提供されているリファレンスタグを基準にして、自動的に出力ファイルの順番を決定します。 また、各ファイルを個別に指定することも可能です。

tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

あるいは、各入力ファイルに対して各ファイルを1つのJavaScriptファイルにコンパイル(デフォルト)することも可能です。 複数のファイルが生成される場合は、Webページ上の<script>タグを使用して、 出力されたファイルを適切な順にそれぞれ読み込む必要があります。

MyTestPage.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" />

エイリアス

2つ目の方法は、名前空間をimport q = x.y.zのようにして使用することで、 通常使用されるオブジェクトを短い名前で表して動作させます。 import x = require("name")文法を使用したモジュール読み込みと混同しないでください。 この文法は単純に特定のシンボルのためのエイリアスを作成します。

あなたは、これらの種類のimport(一般的にはエイリアスとして参照)をモジュールのimportから作成されるオブジェクトも含め、 様々な識別子に対して使用することができます。

namespace Shapes {
    export namespace Polygons {
        export class Triangle { }
        export class Square { }
    }
}

import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // 'new Shapes.Polygons.Square()'と同じ効果

requireキーワードを使用しない代わりに、 直接importされたシンボルの限定的な名前から割り当てていることに注意してください。 これは、varを使用することに似ていますが、 importされたシンボルの型と名前空間の意味付けを引き継いだ上で動作します。 重要なことですが、importされた値は元のシンボルとは個別の参照であるため、 エイリアスされた変数の変更は、元の値に反映されません。

別のJavaScriptライブラリと動作させる

TypeScriptで書かれていないライブラリの型を使用できるようにするために、 ライブラリが公開しているAPIを宣言する必要があります。 多くのJavaScriptライブラリは少量の最上層のオブジェクトのみを公開するため、 名前空間はこれらを表現するのに向いています。

"ambient"の実装を定義していない宣言を呼び出します。(訳注: ambientの具体的な意味が読み取れませんでした。) 通常、これらは.d.tsファイルで宣言されます。 もし、あなたがC/C++に親しんでいれば、.hファイルを思い浮かべるかもしれません。 例を見てみましょう。

Ambient Namespaces

人気のあるライブラリであるD3は、その機能をd3というグローバルオブジェクトで定義しています。 このライブラリは<script>タグ(モジュールローダーの代わりに)を通じて読み込まれるため、 その宣言では型を定義するために名前空間を使用します。 TypeScriptコンパイラがこの型を認識できるように、Ambient Namespace宣言を使用します。 例えば、下記のようにしてこれを書き始めることができます。

D3.d.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;

 Back to top

© https://github.com/Microsoft/TypeScript-Handbook

このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。