今すぐ知るべきTypeScriptのストリングリテラル型


TypeScript1.8の目玉機能と言えば、 **ストリングリテラル（String literal）型** だ。

 * [String Literal Type | TypeScript Deep Dive](https://basarat.gitbooks.io/typescript/content/docs/types/stringLiteralType.html)

ストリングリテラル型は文字列型（`string`型）にさらに制約を加えて、 **特定の文字列のみ許可する** ものだ[^1]。JavaScript的にはただの文字列で、コンパイルタイムに型チェックされる。

## ストリングリテラル型のいい所

何と言ってもJSに多い、引数に文字列キーを渡して処理を分ける関数をちゃんとチェックできるようになるという点。他言語ならenumで処理する所だが、なぜかTypeScriptには数値enumはあるのに文字列enumがなく、有効な手段がとれているとは言いがたかった。

一応これまでも[関数オーバーロード](http://www.typescriptlang.org/Handbook#functions-overloads)の形で、部分的にストリングリテラル型っぽいことは出来ていた。だが、関数を文字列キーの数だけ宣言する必要があり、決して使いやすくはなかった。

ストリングリテラル型の登場で、関数の引数以外にも使える箇所が広がり、文字列enumの扱いはよりやりやすくなった[^2]。

```ts:現在のベターな文字列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);	//補完も効くし、typoもしない！
```

## ストリングリテラル型の問題

さて、文字列enum問題に福音をもたらしたストリングリテラル型だが、意外と問題を抱えている。普通の型と同じように扱おうとすると、結構制限があるのだ。

そこでざっくり調べて表にしてみた。

## <a id="string-literal-table" />ストリングリテラルできるできない表

|項目 |できるできない |補足 |
|:--------|:------|:----|
|ダブルクオートで宣言`"`|○||
|シングルクオートで宣言`'`|○||
|テンプレートストリングスで宣言|×|[*](#template-strings)|
|変数・定数(`var,let,const`)型宣言|○||
|プロパティ型宣言|○||
|関数/メソッド返り値の型宣言|○||
|関数/メソッドの引数の型宣言|△|[*](#single-typed-func-arg)|
|デフォルト/オプショナル引数|△|[*](#single-typed-func-arg)|
|型アサーション`<"a">`/`as`演算子|○||
|配列 `"a"[]`|○||
|ジェネリックス `Class<"a">`|○||
|型パラメータの制約 `<T extends "a">`|○||
|`string`型への暗黙のキャスト|○|[*](#cast-to-string-type)|
|`string`型からの暗黙のキャスト|×|[*](cast-from-string-type)|
|インデックスシグネチャ|×|[*](#index-signature)|
|型クエリ `let a:typeof "a"`|×||
|継承 `class C extends "a"{}`|×||
|タイプエイリアス|○||
|Union型 |○||
|Intersection型 `"a"&"b"`|○|[*](#intersection-type)|
|タプル型 `let a:["a","b"]`|○||
|Type Guard|×|[*](#type-guard)|
|User Defined Type Guard|○|[*](#user-defined-type-guard)|
|IDEの補完サポート|×|[*](#completion-on-IDE)|
|IDEのシンボルリネームサポート|×|[*](#symbol-rename-on-IDE)|
|`const`宣言時の型推論|×|[*](#type-inference-const)|

TypeScript ver. 1.8.7

# できること

## <a id="cast-to-string-type" />string型への暗黙のキャスト

```ts
let A:"a" = "a";
let a:string = A;	//ok
```

string型へは特に何もせずキャスト可能（型アサーション不要）。逆は不可。

## <a id="user-defined-type-guard" />User Defined Type Guard

```ts
function isA(a:any): a is "a"{
	return true;
}

function fnUserAorB(arg:"a"|"b"){
	if(isA(arg)){
		let a:"a" = arg;
	}else{
		let b:"b" = arg;
	}
}
```

[普通のタイプガードはできない](#type-guard)が、ユーザー定義タイプガード関数を使えばいける。`is`演算子も問題ない。

# できるけど意味ないこと

## <a id="intersection-type" />交差型

```ts
type A = "a" & "b";	//定義可能

let a:A;
a = "a";	//error
a = "b";	//error
a = "ab";	//error
```

交差型は定義できるが、これに当てはまる値が作れない。
まあ`"a"&"a"`みたいなのであればいけるが、意味が無い。

# できないこと

## <a id="template-strings" />テンプレートストリングスで宣言

```ts

type DQ = "a\nb";	//ok
type SQ = 'a\nb';	//ok
type TS = `a\nb`;	//error
```

テンプレートストリングスの書き方ではストリングリテラル型として宣言できない。

## <a id="cast-from-string-type" />string型からの暗黙のキャスト

```ts
const s:string = "s";
let s2:"s" = s;	//error!
let s3:"s" = <"s">s;	//ok
let s4:"s" = s as "s";	//ok
```

逆はできるが、これは例え同じ文字列でもできない（要・型アサーション）。

## <a id="type-guard" />Type Guard

```ts

function fnAorB(arg: "a"|"b"){
	if(typeof arg === "a" ){	//typeof arg === "string"
		let a:"a" = arg;	//error!
	}else{
		let b:"b" = arg;	//error!
	}
}

function fnAorB2(arg: "a"|"b"){
	if(arg instanceof "a"){	//error!
		let a:"a" = arg;	//error!
	}else{
		let b:"b" = arg;	//error!
	}
}

function fnAorB3(arg: "a"|"b"){
	switch(arg){
		case "a":{
			let x:"a" = arg;	//error!
			break;	
		}
		case "b":{
			let x:"b" = arg;	//error!
			break;
		}
	}
}
```

タイプガードには使えない。

`fnAorB()`,`fnAorB2()`のように、typeof演算子やinstanceof演算子で扱えるものではない。

関連Issue: [enable type guards over string literal types #6028](https://github.com/Microsoft/TypeScript/issues/6028)

## <a id="single-typed-func-arg" />関数の引数の型指定に一つだけ指定

```ts:エラーになる指定方法
function f(a:"a"){}	//error!関数オーバーロード扱い
```

関数/メソッドの引数の型指定にそのまま一つのストリングリテラル型を指定してもエラーになる（関数/メソッドオーバーロードと被る為）。ただしくは以下の方法を使う。

```ts:括弧でくくる
function f(a:("a")){}
```

```ts:TypeAliasでもOK
type A = "a";
function f(a:A){};
```

```ts:Union型や交差型でならOK
function f1(a:"a"|"a"){};

function f2(a:"a"&"a"){};
```



実際の利用シーンでは一つだけのストリングリテラル型というのも考えにくいのであまり問題ではないが、覚えておくべき。

```ts:
function defaultParamFunc(a:number, b:("b") = "b"){}
function optionalParamFunc(a:number, b?:("b")){}
```
上記ルールに従えば、デフォルト引数、オプショナル引数両方とも型指定可能。


## <a id="index-signature" />インデックスシグネチャ

```ts
type A = "a";
type O = {
	[s:A]:number;	//error!
};
```

インデックスシグネチャは`string|number`と定義されておりストリングリテラル型は指定できない。 

関連Issue: [String literal types as index signature parameter types? #5683](https://github.com/Microsoft/TypeScript/issues/5683)

## <a id="completion-on-IDE" />IDEでの補完

VSCodeなどで、選択肢が絞り込めても補完できない。型として扱うメリットの半分くらい無い感じである。

関連Issue: [Provide completion lists for string literals #606](https://github.com/Microsoft/TypeScript/issues/606)

## <a id="symbol-rename-on-IDE" />IDEでのシンボルリネーム

VSCodeだと、変更文字列を入力まではできるがその先が機能しない。

関連Issue: [String Literal refactoring support #5602](https://github.com/Microsoft/TypeScript/issues/5602)

## <a id="type-inference-const" />const宣言時の型推論

```ts
const A = "A";		//Aはstring型
const B:"B" = "B";	//Bは"B"型

let a:"A";
a = A;	//error!
let b:"B";
b = B;	//ok
let c:string;
c = B;	//ok
```

`const`で宣言した文字列変数・定数は不変。にもかかわらず、現時点では型指定をしっかり書かないとstring型として扱われ、型エラー。なかなか不便である。

関連Issue: [Mutability widening for literal types #6554](https://github.com/Microsoft/TypeScript/pull/6554)

上記の pull request が実装されると冒頭の文字列enumは以下のように書けるようになるはず。

```ts:#6554が取り込まれたらこう書ける
type Enum = "a" | "b" | "c";
namespace Enum{
	export const A = "a"
	export const B = "b"
	export const C = "c"
}
```


# まとめ

 * ストリングリテラル型は便利
 * でも、既存の型と全く同じに扱える訳じゃない（[表](#string-literal-table)）

# ※追記

 * TS2.0.3でString以外のリテラル型が追加された
  * http://qiita.com/vvakame/items/826bf193dd301862014f#literal-types%E3%81%AE%E6%8B%A1%E5%A4%A7

[^1]: C++やDで「文字列リテラル型」と言われているものとは別。flowtypeが[ほぼ同じもの](http://flowtype.org/docs/builtins.html#literal-types)を既に実装している。
[^2]: 参考：[TypeScriptでのイベント名を管理・指定するもう一つの方法(＋ストリングリテラル型にも対応)](http://qiita.com/ConquestArrow/items/02826db3ddbe98d280bd)