2016年9月にTypeScript 2.0がリリースされた。本稿では、これまでのTypeScriptの進化の過程とバージョン2.0で導入された新機能を見てみよう。
2014年4月にバージョン1.0がリリースされてから約2年半のうちに、TypeScriptはめざましい進化を遂げ、2016年9月にはバージョン2.0がリリースされた。以下にTypeScriptの進化の過程をまとめておこう。
バージョン | 主な新機能 |
---|---|
1.3 | protected修飾子、タプル型 |
1.4 | ES6サポート(let/constキーワードとテンプレート文字列)、共用型(Union Types)、型ガード(Type Guards)、型エイリアス、const enum、--noEmitOnErrorコマンドラインオプション |
1.5 | ES6サポート(モジュール、destructuring、for…of構文、シンボルなど)、namespaceキーワード(1.4以前の内部モジュールの置き換え)、デコレータ(将来のESで導入予定)、tsconfig.jsonファイル |
1.6 | 交差型(Intersection Types)、ローカル型/ローカル宣言、クラス式、抽象クラス/抽象メソッド、ジェネリック型の型エイリアス、ES6ジェネレーターのサポート、async関数の試験的サポート |
1.7 | async/awaitサポート(ES6をコンパイルターゲットとした場合)、thisの型推論、べき乗演算子 |
1.8 | 型パラメーターを制約として利用、制御フロー解析によるエラー検出、Augmenting global/module scope from modules、文字列リテラル型 |
TypeScriptの進化の過程 |
これまでにTypeScriptに追加されてきた機能の多くはECMAScriptの言語仕様を取り込むものと、ECMAScriptに対して型注釈をコアとする静的型システムを付加するものに大別される。特にバージョン1.4と1.5では、ECMAScript 2015対応が進められ、バージョン1.6以降では型システムの強化が行われたといえる。この型システムの強化により、静的型情報を持つ言語ならではの形で、TypeScriptレベルで堅牢なコードを簡便に記述できるようになっている。
そして、TypeScript 2.0では、これらの進化を踏まえた上で次のような機能が拡張されている(抜粋)。
なお、各バージョンにおいて行われた言語仕様に対する破壊的な変更についてはGitHub上のTypeScriptリポジトリの「Breaking Changes」ページを参照されたい。
本稿では、TypeScriptの型システムを中心にバージョン2.0での強化点やその理解に必要な以前のバージョンの追加機能を概観していく(上の表および箇条書きに示した全ての要素を取り上げるわけではない)。
なお、TypeScriptの基本については少々古い記事になるが「TypeScriptで学ぶJavaScript入門」や「特集:TypeScript(プレビュー版)概説(前編)」「特集:TypeScript(プレビュー版)概説(後編)」も参照されたい。基本データ型やクラス定義、関数定義の方法、制御構造など基本的な構文要素については上記の記事が詳しいので、本稿ではそれらの説明は省略する。
また、TypeScript 2.0で追加された要素については、見出しの先頭に[TS2.0]と含めるようにする。まずは以前のバージョンのおさらいがてらTypeScript 1.4で登場した共用型と型ガードについて見てみよう。
共用型*1(Union Types)とは、変数が参照するオブジェクトが複数の型のいずれかであることを示す型のことだ(バージョン1.4で追加)。以下に例を示す。
let value: string | number;
value = "100";
value = true;
*1 「共用型」という訳語は「TypeScript早わかりチートシート【1.5.3対応】」から。チートシートの筆者でもあるvvakame氏による「Revised 型の国のTypeScript」では「直和型」と表記されているが、本稿では「共用型」を使用する。
上のコードで示したように共用型はパイプ記号で区切って、その変数が参照する(その変数に代入可能な)オブジェクトの型を並べていく。Visual Studio Codeで上のコードを入力すると、変数valueの型は「string」か「number」のいずれかであるため、「value = true;」行でエラーが発生する。エラーメッセージの意味は「string型もしくはnumber型の変数にはブール値は代入できない」である。
共用型の使い道の1つとしては、引数の数が一定でその型が異なる場合の関数やメソッドのオーバーロードが考えられる。通常は次のようなコードを書く。
function foo(param: string): string;
function foo(param: number): number;
function foo(param): any {
if (typeof param === "string") {
return "hello " + param;
} else if (typeof param === "number") {
return param * 2;
}
}
この関数はstring型もしくはnumber型のパラメーターを1つだけ持ち、その型に応じて動作を変えている。先頭の2行は「オーバーロードリスト」といい、この関数はオーバーロードされていて、2種類のバージョンがあることと、それぞれのバージョンのパラメーターリストと戻り値の型を示している。本来はシンプルな関数ではあるが、見た目にはかなり面倒なコードになっている。共用型を使用すると、これは次のように書ける。
function foo(param: string | number): string | number {
if (typeof param === "string") {
return "hello " + param; // パラメーターparamの値はstring型
} else {
return param * 2; // パラメーターparamの値はnumber型
}
}
console.log(foo("world"));
console.log(foo(100));
まず、オーバーロードリストを記述していない。それから、パラメーターparamが共用型になっている。つまり、その値はstring型かnumber型になる。そして、もう1つ、このコードでは後述する「型ガード」と呼ばれる共用型と同時にTypeScript 1.4で導入された機能も使って、関数本体での処理が簡潔に記述されている。以下にVisual Studio Codeでのコード記述例を示す。
上の画像の最下行で「foo(true)」呼び出しがエラーとなっていることにも注目しよう。関数fooはパラメーターにboolean型の値を受け取らないことが明記されていることから、Visual Studio Codeがこれを検出してくれている(上記の型の特定も含めて、これらはTypeScriptの言語サービスのおかげだ)。
共用型の活用例の1つとして、ここでは関数オーバーロードを挙げたが、パラメーター数が多数ある、あるいは関数ごとにパラメーター数が異なる場合には、素直にオーバーロードリストを記述して、そのシグネチャを明記するようにした方が自分にも他の利用者にもやさしいかもしれない(オーバーロードする場合でも、共用型を使う場合でも、関数本体の実装は同じような記述になるとは思うが)。
型ガード(Type Guards)とは、if文での型比較により、比較対象となった変数の型を特定することである。共用型と同様にTypeScript 1.4で導入されている。typeof演算子とinstanceof演算子を使う場合がある。
例えば、先ほどのコードでは「if (typeof param === "string")」によりそのif節内ではパラメーターparamの値がstring型に特定される。逆にelse節では、パラメーターparamの値は「string型でない方」である「number型」に特定され、安全にそれぞれの型に依存した処理を記述できている。型ガードがないと先ほどのコードは型アサーション*2(キャスト)を使って以下のように書く必要がある。
function foo(param: string | number): string | number {
if (typeof param === "string") {
return "hello " + <string>param;
}
return <number>param * 2;
}
つまり、if文でのtypeof/instanceof演算子での型識別の後には「型は今チェックしたんだから、安全にその型を使えるよね」と比較対象の型を特定するのが型ガードだ。このように型ガードでは型推測をより積極的に行うことで、不要な型アサーションを行うことなく、より簡潔に(そして、開発者の直観に沿った)コードを記述できるようになっている。
*2 型アサーションとは他の言語におけるキャストと同様だ。TypeScriptでは変数名の前に「<型名>」と記述するか、「変数名 as 型名」と記述することで、その変数が参照するオブジェクトを特定の型として扱うことをコンパイラに教えてやる。
instanceof演算子を使った型ガードの例も示しておこう。
class Foo {
foo_method() {
console.log("method on foo");
}
};
class Bar {
bar_method() {
console.log("bar method");
}
};
type t = Foo | Bar; // 型エイリアス
function foo(x: t) {
if (x instanceof Foo) {
x.foo_method(); // xはFooクラスのインスタンス
} else {
x.bar_method(); // xはBarクラスのインスタンス
}
}
foo(new Foo());
foo(new Bar());
「type t = Foo | Bar;」というのは「型エイリアス」を定義している。ここでは型tは、Foo型かBar型かのいずれかを取る共用型である。型エイリアスもバージョン1.4で登場した機能だ。
そして、TypeScript 2.0では共用型を拡張した「タグ付き共用型」が登場した。次ページではこれを見てみよう。次ページからはTypeScript 2.0の新機能を主に取り上げる。
Copyright© Digital Advantage Corp. All Rights Reserved.