namespaceはクラスの拡張に使える
TypeScriptのnamespace
1は本来の名前空間を分けるという事以外に、クラスを拡張してプロパティを生やすことができる。これを使うと本来文法的にできない、あるいは出来るけど通常の処理のコードと定義している事を明示化してメンテナンス性を向上する事が出来る。
具体的には、以下のルールに従って定義する。
- class→namespaceの順に同じ名前で定義する
- namespaceで定義するプロパティは全てexport
- classをexportするならnamespaceもexportする(合わせる)
ただし、 クラスではない組み込みオブジェクトを「拡張」することはできない2 (オブジェクトが上書きされてしまう)。また、 別モジュールからimportやrequireしたものも無理である (多重定義扱いでエラー)。
1. class→namespaceの順に同じ名前で定義する
class A{}
namespace A{
export const CONST_PROP = "prop";
}
//namespaceを先に定義するのはダメ
namespace A{ //error!
let a = 1;
}
class A{}
2. namespaceで定義するプロパティは全てexport
class A{}
namespace A{
let propA = 1;
export let propB = 2;
}
A.propA; //error!
A.propB; //2
3. classをexportするならnamespaceもexportする(合わせる)
export class B{} //error!
namespace B{
export let prop = "prop";
}
class C{} //error!
export namespace C{
export let prop = "prop";
}
活用例
クラス定数
readonly
を待たなくてもnamespace
を使うとクラス定数が実現できる。
class A{}
namespace A{
export const CONST_PROP = "prop";
}
A.CONST_PROP; //ok
- メリット
- 定数管理専用の名前を考える必要が無い(クラスと一緒に扱える)
- TS2.0で予定の
readonly
の実装を待たなくていい - デメリット
- ランタイムではただのクラスstaticプロパティ(上書き可能)
- classと一緒に書けず、後からの定義(定数なら先に書きたい)
- IDE上でclassとnamespace、二重の情報がでる
クラスプロパティ
クラス定数ができるなら、もちろんクラスプロパティもできる。
class A{}
namespace A{
export let prop = "A's prop";
}
A.prop; //ok
- メリット
- クラスと一緒に扱える
- 定義している箇所を明示化でき、メンテナンス性が向上
- デメリット
- classと一緒に書けず、後からの定義
- IDE上でclassとnamespace、二重の情報がでる
拡張メソッド
C#にある「拡張メソッド」をTypeScriptでやるにはどうしたらいいのか?という疑問がC#から入ってきた人を中心にたまに見る。TypeScriptのnamespace
はC#の拡張メソッド相当のこともできる。
class Sample{} //対象となるクラス
//...
namespace Sample{
export function fn(){}
}
Sample.fn(); //ok
- メリット
- 定義している事がわかりやすい(
Sample.fn = () => {...}
とどこかの1行で定義するより明示的で、修正時にも見つけやすい) - デメリット
- とはいえ、最初からstaticでクラス内で定義できるならそっちの方が分かりやすい3
-
export
,function
とコード量が多くなる
豆知識編
インスタンスメソッド、インスタンスプロパティ
class
→ interface
→ namespace
の順に定義すればインスタンスメンバー(メソッド、プロパティ)も拡張できる。
class A{}
//クラスと同名のインターフェイスを定義
interface A{
//追加したいインスタンスメンバー
fn():void;
prop:string;
}
namespace A{
A.prototype.fn = () => {};
A.prototype.prop = "prop";
}
ただし、namespace
で括らなくても同じなので、明示化する以外のメリットはない。後からクラスを拡張したくなったときに、staticなメンバーと一緒に定義するのが分かりやすくなる、といったケースで役立つぐらいか。
関数やenumを拡張
実用性は低いが関数やenumも拡張できる。
function test(){
return "test";
}
namespace test{
export let prop = "prop";
}
console.log(test()) //"test"
console.log(test.prop); //"prop"
enumの場合、number型以外のプロパティを生やせる。しかし、型チェックに使える訳ではないので実用性はかなり低い。
enum Enum{
A,
B,
C = 100
}
namespace Enum{
export const STRING = "string";
export function func(){
return Math.PI;
}
}
function testEnum(arg:Enum){console.log(arg)};
testEnum(Enum.A);
testEnum(Enum.STRING); //error
testEnum(Enum.func()); //numberを返すならok
TypeAlias
を拡張
TypeAliasを拡張する事も出来る。
これとストリングリテラル型を組み合わせると、文字列Enumが同じ名前で管理できる。
type Enum = "a" | "b" | "c";
namespace Enum{
export const A:Enum = "a"
export const B:Enum = "b"
export const C:Enum = "c"
}
function enumOnly(arg:Enum){}
enumOnly(Enum.A);
参考:今すぐ知るべきTypeScriptのストリングリテラル型