LoginSignup
9
4

More than 5 years have passed since last update.

TypeScript Handbook を読む (14. Namespaces)

Last updated at Posted at 2017-05-13

TypeScript Handbook を読み進めていく第十四回目。

  1. Basic Types
  2. Variable Declarations
  3. Interfaces
  4. Classes
  5. Functions
  6. Generics
  7. Enums
  8. Type Inference
  9. Type Compatibility
  10. Advanced Types
  11. Symbols
  12. Iterators and Generators
  13. Modules
  14. Namespaces (今ココ)
  15. Namespaces and Modules
  16. Module Resolution
  17. Declaration Merging
  18. JSX
  19. Decorators
  20. Mixins
  21. Triple-Slash Directives
  22. Type Checking JavaScript Files

Namespaces

原文

First steps

コード例として、ユーザ入力や外部ファイルをバリデーションするための簡単な文字列バリデータを使用します。

バリデータ(単一ファイルの場合)
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"];

// 使用するバリデータ
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 }'.`);
    }
}

Namespacing

バリデータを追加するにつれて、バリデータの管理や名前の衝突の回避のためになんらかの方法でバリデータを整理したいと思うかもしれません。
その場合、個別に名前を付けてグローバル名前空間に配置する代わりに、オブジェクトを名前空間でラップしてみましょう。

先ほどの例について、バリデータに関係するコードを Validation に移動し、外部に公開するインタフェース/クラスに export を付与します。
逆に、lettersRegexpnumberRegexp は実装の詳細であるため、エクスポートしないようにします。
名前空間の外からバリデータを使用するためには、名前空間名付きで指定する必要があります。(例: Validation.LettersOnlyValidator)

バリデータ(名前空間あり)
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"];

// 使用するバリデータ
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 各文字列がバリデーションを通過したかどうかを表示する
for (let s of strings) {
    for (let name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}

Splitting Across Files

Multi-file namespaces

次に、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" />

// チェック対象
let strings = ["Hello", "98052", "101"];

// 使用するバリデータ
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 各文字列がバリデーションを通過したかどうかを表示する
for (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

それ以外の方法として、各ファイルを個別の JavaScript ファイルに出力 (デフォルト) することも可能です。
その場合、<script> タグを使用し、適切な順番で JavaScript ファイルを読み込む必要があります。

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

Aliases

名前空間を簡単に取り扱う別の方法として、import q = x.y.z を使用して短縮名を作ることが可能です。

TypeScript
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 を使用する代わりに、シンボルの修飾名を直接代入していることに注意してください。
var を使用した変数宣言に似ていますが、型や名前空間にも使用することができます。
重要なポイントとして、値に対して import を使用した場合、元のオブジェクトとは区別して扱われるため、値を変更しても元の値には反映されません。

Working with Other JavaScript Libraries

TypeScript で書かれていないライブラリについては、そのライブラリの提供する API を宣言する必要があります。
この実装を伴わない宣言のことを "ambient" と呼んでいます。

Ambient Namespaces

有名なライブラリである D3 は d3 というグローバルオブジェクトを提供しています。
このライブラリは (モジュールローダーを使用せずに) <script> タグ経由で読み込まれるため、ライブラリの宣言に名前空間を使用しています。
TypeScript からこの宣言を参照するには、アンビエント名前空間を使用します。

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;

declare 〜 がアンビエント宣言部分

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4