Deep Dive
あなたが望む正確なAPI形状(shape)でモジュールを構造化するのは、難しいことがあります。
例えば、new有り/無しで異なる型を呼び出すことが可能で、
階層構造で公開される様々な名前付きの型を持ち、同様にモジュールオブジェクト上に幾つかのプロパティを持つモジュールが必要な場合があるでしょう。
このガイドを読むことで、フレンドリーなAPIを公開する複雑な定義ファイルを作成する術を身につけることができるでしょう。 このガイドでは、オプションがより多様であるという理由から、モジュール(またはUMD)ライブラリに焦点を当てています。
主要概念
TypeScriptの仕組みの主要概念を理解することで、どのように定義するかを完全に理解することができます。
型
このガイドを読んでいるのなら、TypeScriptの型がどのようなものか、おおよそ理解されていることでしょう。 ただし明確にするために、いま一度型について確認しておきましょう。
-
型のエイリアス宣言(
type sn = number | string;) -
インターフェース宣言(
interface I { x: number[]; }) -
クラス宣言(
class C { })) -
enum宣言(
enum E { A, B, C }) -
型を参照する
import宣言
これらの各宣言の形式は、新しい型の名前を作成します。
値
型と同様に、値が何であるかは既に理解していることでしょう。
値は式で参照することができる実行時の名前です。
例えば、let x = 5;はxと呼ばれる値が作成されます。
再び明確にするために、次のようにして値が作られることを確認しましょう。
-
let、const、var宣言 -
値を含む
namespaceまたはmodule宣言 -
enum宣言 -
class宣言 -
値を参照する
import宣言 -
function宣言
名前空間
型は名前空間の中に存在することができます。
例えば、let x: A.B.Cを宣言した場合、型CはA.B名前空間にあると言えます。
この区別は微妙で重要です。ここでのA.Bは必ずしも型または値ではありません。
単純な混合(combination): 1つの名前による複数の意味
Aという名前が与えられると、Aは型、値、名前空間の3つの異なる意味を見出すことができます。
名前の解釈方法は、それが使用されるコンテキストによって異なります。
例えば、let m: A.A = A;という宣言では、1つ目のAが名前空間として、次が型名として、次が値として使用されます。
それぞれが、まったく異なる宣言を参照することになるのです!
紛らわしいと感じるかもしれませんが、やり過ぎにならない限り、実際には非常に便利です。 この混合(combination)による動作の有益な側面を見てみましょう。
組み込みの混合(combination)
鋭い読者であれば、例えばクラスが型と値の両方に現れることに気付くでしょう。
class C { }の宣言は、クラスの形状(shape)のインスタンスを参照する型Cと、
クラスのコンストラクタ関数を参照する値Cの2つを作成します。
Enum宣言も同様に動作します。
ユーザーによる混合(combination)
次のようなfoo.d.tsがあるとします。
export var SomeVar: { a: SomeType };
export interface SomeType {
count: number;
}
そして、次のように使用するとします。
import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);
これは問題なく機能しますが、SomeTypeとSomeVarが非常に密接な関係にあるため、
同じ名前にしたいと思うかもしれません。
これら2つの異なるオブジェクト(値と型)を提供するために、混合(combination)を使用することができます。
同じ名前Barを使用することとします。
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
これは、使用する際に優れた使い分けを提供してくれます。
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
ここでも、型と値の両方としてのBarを使用しました。
Barの値はBar型であると宣言する必要がなく、独立していることに注目してください。
混合(combination)の上級編
いくつかの種類の宣言を、複数の宣言に渡って混合させることが可能です。
例えば、class C { }とinterface C { }は共存可能であり、
どちらも型Cにプロパティを提供します。
これは、衝突が発生しない限り、合法であると言えます。
一般的な経験則で言えば、値は名前空間として宣言されていない限り、常に同じ名前の他の値と衝突します。
型のエイリアス宣言(type s = string)で宣言された場合は型が衝突し、名前空間は衝突しません。
これがどのように使用されるかを確認してみましょう。
インターフェースを使用して追加
別のインターフェース宣言を使用して、インターフェースにメンバを追加することができます。
interface Foo {
x: number;
}
// ... どこか他の所で ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
また、これはクラスを使用しても動作します。
class Foo {
x: number;
}
// ... どこか他の所で ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
インターフェースを使用して、型エイリアス(type s = string;)を追加できないことに注意してください。
名前空間の使用を追加
名前空間宣言を使用することで、新しい型、値、名前空間を衝突を起こさないように追加することができます。
例えば、クラスに静的(static)メンバを追加することができます。
class C {
}
// ... どこか他の所で ...
namespace C {
export let x: number;
}
let y = C.x; // OK
この例では、Cの静的(static)サイド(コンストラクタ関数)に、値を追加したことに注目してください。 これは値を追加しましたが、これら全ての値のコンテナが、別の値であるためです。 (型は名前空間に格納され、名前空間は別の名前空間に格納されます)
名前空間に入れられた型を、クラスに追加することも可能です。
class C {
}
// ... どこか 他の所で...
namespace C {
export interface D { }
}
let y: C.D; // OK
この例では、この名前空間の宣言を書くまで、名前空間Cは存在しません。
これは、名前空間としてのCは、クラスによって作成されたCの値または型と衝突しないことを意味します。
最後に、名前空間宣言を使用した様々なマージの実行を確認してみましょう。 現実的な例ではありませんが、様々な興味深い動作を見ることができます。
namespace X {
export interface Y { }
export class Z { }
}
// ... どこか他の所で ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
この例では、1つ目のブロックは次の名前の意味を作成します。
-
値
X(namespace宣言が、値Zを含むため) -
名前空間
X(namespace宣言は、型Yを含むため) -
X名前空間内の型Y -
X名前空間内の型Z(クラスのインスタンス形状(shape)) -
X値のプロパティである値Z(クラスのコンストラクタ関数)
2つ目のブロックは次の名前の意味を作成します。
-
X値のプロパティである値Y(number型) -
名前空間
Z -
X値のプロパティである値Z -
X.Z名前空間内の型C -
X.Z値のプロパティである値C -
型
X
export = または importの使用
重要なことは、exportとimport宣言が、
それら対象の全ての意味付けをインポートまたはエクスポートするということです。
© https://github.com/Microsoft/TypeScript-Handbook
このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。
- ドキュメントの情報が古い可能性があります。
- "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。