TypeScriptの文字列定数の管理方法



文字列定数は便利だ。

```javascript:ES6/TypeScript1.4以降
const CONST_STR = "str";
```

 * typoが防げる
 * 補完の効くエディタでタイプ量が減る
 * リネームなどのリファクタリングが容易になる

問題は数が増えてきた時。

```javascript:同じスコープで管理するとこうなりがち
const UNIQUE_NAME_STR1 = "...";
const UNIQUE_NAME_STR2 = "...";
const UNIQUE_NAME_STR3 = "...";
const UNIQUE_NAME_STR4 = "...";
const UNIQUE_NAME_STR5 = "...";
//...
const UNIQUE_NAME_STR100 = "...";
```

同じスコープで全部定義すると、定数名が長くなってしまう。
またエディタの補完が効くといっても、今度は候補数が多すぎて選ぶのが大変だ。

そこで分類毎にスコープを分けて管理・定義することにする。
`分類.CONST`のようにしたい。TypeScriptではどうするのが良いか。

## × class

さいしょは`class`を使おうと考えたが、`class`の **プロパティとして文字列定数は定義できない** [^1]。 

```javascript:staticなconstプロパティを定義できない
class myConst{
	static const STR1 = "...";	//error!
}
```

こうすればできる。が、これは **変数** だ。書き換えられては困る。

```javascript:これは変数
class myConst{
	static STR1 = "...";	//書き換え可能
}
```

書き換えられなければいいんだな?ということで`getter`を定義してみるが、あまりにも冗長だ。関数呼び出しのパフォーマンス低下も気になる。

```javascript:getterは機能は満たすが書き方が冗長
class myConst{
	static get STR1(){return "...";}
}
```

というわけで機能的には使えるが、採用しなかった。

## × interface

TypeScriptの`interface`は他の言語に比べると機能が多いのでできないかなと思ったが、そもそも定義の実体を書けないのでムリ。

## × object

`class`と同じくgetterプロパティを定義すればいいが、めちゃくちゃ冗長になるのでナシ。

```javascript:ないわー
var myConst = {};
Object.defineProperty(myConst, "STR1", {
    get: () => "...",
    enumerable: true,
    configurable: true
});
```

**※追記**：`get`キーワード使えた。冗長なのはclassと同じ。

```
var myConst = {
	static get STR1(){return "...";}
};
```

## ○ 内部モジュール(module) / namespace

試行錯誤の上採用した方法は内部モジュール(`module`)を使う方法だった。外部から使うので`export`とだけつける必要があるが、冗長な書き方も必要なくスッキリかける。

```javascript:これだよこれ！
module myConst{
	export const STR = "...";
}
```

そもそも同一スコープで、が問題の原因だったので、スコープをわけるための機能を使えばいい、という分かれば何ともない話だった。

## 分類毎に分けてみる

TypeScriptはver.1.5から`namespace`という機能が使える。単に内部モジュールと同じものなのだが、別物の内部モジュール・外部モジュールに同じ言葉で説明する混乱がなくなるので、まさにやりたい事の名前をしている`namespace`を使ってみる。

簡単なライブラリっぽいものを作ったと仮定してみるとこんな感じ。

```javascript:namespaceで分類別に文字列定数を管理する
/** ライブラリの文字列定数 */
namespace myConst{
	export const NAME = "myLibrary";
	export const LICENSE = "MIT";
	export const VERSION = "1.0.1";
	export const PATH = "http://example.jp/path/to/"
	//...
}
/** Event種別の文字列定数 */
namespace myConst.Event{
	export const LOAD = "load";
	export const AWAKE = "awake";
	export const CLICK = "click";
	//...
}
/** ロゴ関係の文字列定数 */
namespace myConst.Logo{
	export const PATH = "path/to/image";
	export const ALT = "myLibrary logo.";
	//...
}

console.log(myConst.Logo.ALT);	//"myLibrary logo."
```

 * 構造化されて奇麗で見やすい
 * 定数の文字数が最小限
  * `LIB_URL_PATH`と`LOGO_PATH`に分けなくてもいい
 * 定数だけ管理しているので無駄な補完候補がでない

`namespace`(内部モジュールも)は入れ子ができる。これをうまく使って奇麗に管理できる[^2]。分かりやすくて使いやすくて安心だ。

## 補足：深い階層を作ってしまったとき

調子に乗って分類しすぎてしまった場合や、名前空間のバッティングを避けて深い階層のものを作った場合。

```javascript:めちゃくちゃ階層が深い
namespace myConst.long.long.long.very.very.very.long.path{
	export const STR = "...";
}
//usage
myConst.long.long.long.very.very.very.long.path.STR;
```

いくら補完が効いてもこの長さは厳しい。

そんなときは変数や定数に代入してしまうといい。

```javascript:長くても安心！
const m = myConst.long.long.long.very.very.very.long.path;
console.log(m.STR);	//"..."
```

`import`文でもほぼ同じ。

```javascript:importで
import m2 = myConst.long.long.long.very.very.very.long.path;
console.log(m2.STR);	//"..."
```

意味的には`import`の方がいいかも？


[^1]: 文字列に限らず定数全般。クラスに定数が定義できないのは不便だ。機能(メソッド)とデータ(プロパティ)をひもづけるのがクラスだとすれば、定数も立派なデータだと思うのだが、TypeScriptは現状クラスだけで取り回しできない。
[^2]: この辺の管理方法とかあまりTypeScriptのドキュメントで解説されてない気がするが、もうみんなJavaやC#の知識あるから説明不要だよね、ということなのだろうか。