前回はTypeScriptの魅力を3つほど紹介し、TypeScriptの開発環境を構築しました。今回はTypeScriptの基本文法とTypeScriptの魅力の一つであり、TypeScriptを最も特徴付ける機能である型注釈について紹介します。
関数
型注釈の説明に入る前に、TypeScriptの関数について説明します。
前回強調したとおり、「JavaScriptのコードそのまま解釈でき、既存コードから開発をスタートできる」ということは、TypeScriptの特徴の一つです。なので、シンタックスはJavaScriptのコードと大差ありませんが、より使いやすく拡張されているものもあります。特に関数は様々な拡張がなされています。
具体的には次の4つの機能が利用できます。
- オプショナルパラメータ
- デフォルトパラメータ
- 可変長パラメータ
- ラムダ式
オプショナルパラメータ
JavaScriptでは宣言されているパラメータの数と、実際にコールする際のパラメータの数は違っていても問題はありませんでした。
|
1 2 3 4 5 |
function func(arg1, arg2){ } func(); // なくてもok func(0); // 足りなくてもok func(0,1,2); // 多くてもok |
しかし、TypeScriptは静的型付け言語であり、宣言通りに値を渡さなければコンパイルエラーとなります。
|
1 2 3 4 5 6 |
function func(arg1, arg2){ } func(0,1); // ok func(); // コンパイルエラー func(0); // コンパイルエラー func(0,1,2); // コンパイルエラー |
このため、TypeScriptではJavaScriptと同様にパラメータに値を渡さないことを許容するには、 ?を用いて明示的に宣言をする必要があり、これをオプショナルパラメータと呼びます。
|
1 2 3 4 5 6 7 8 |
function func(arg1, arg2?){ if(!!arg2){ } } func(0,1); // ok func(); // コンパイルエラー func(0); // ok func(0,1,2); // コンパイルエラー |
オプショナルパラメータは ?をパラメータの後に付け、宣言し、値が渡されたどうかは実装で動的に検査する必要があります。
デフォルトパラメータ
さらに、TypeScriptではECMA-262 6th Editionの仕様の一つであるデフォルトパラメータが利用できるため、関数にデフォルトのパラメータを設けることができます。
|
1 2 3 4 5 |
function func(arg = "default"){ return arg; } func("hello"); // "hello" func(); // "default" |
デフォルトパラメータは =を用いて定義します。また、デフォルトパラメータはオプショナルパラメータと同様に扱われ、コールする際にパラメータを渡さなくともエラーにはならず、デフォルトの値が代入されます。
レストパラメータ
JavaScriptでは可変長パラメータを実現するために、 argumentsオブジェクトを利用していました。しかしTypeScriptでは、ECMA-262 6th Editionの仕様の一つである、レストパラメータを利用できます。
|
1 2 3 4 |
function func(arg, ...args){ return args; } func(0,1,2,3); // [1,2,3] |
レストパラメータは先頭に ...を付け、定義します。 また、レストパラメータが宣言できるのは宣言内の最後のパラメータのみで、値は配列として格納されます。
ラムダ式
最後にラムダ式です。JavaScriptの関数は関数内と外で thisの参照先が異なりました。よって、JavaScriptでは thisを変数に格納しておいて、その変数から参照する方法がよく使用されています。 次のコードでは ringPallet関数にバインドされる thisの参照を selfにコピーし、戻り値の関数内で使用しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var Color = { code: ["#000", "#FFF", "#F00", "#0F0", "#00F"] , ringPallet: function( ){ var self = this , index = 0; return function( ){ var color = self.code[index]; index = (index + 1) % self.code.length; return color; } } } var nextColor= Color.ringPallet(); nextColor(); // "#000" nextColor(); // "#FFF" |
しかし、TypeScriptではラムダ式を使用することで thisの参照先を同一にすることができます。
|
1 2 3 4 5 6 7 8 9 10 11 |
var Color = { code: ["#000", "#FFF", "#F00", "#0F0", "#00F"] , ringPallet: function( ){ var index = 0; return ( ) => { var color = this.code[index]; index = (index + 1) % this.code.length; return color; } } } |
ラムダ式は次のように =>を用いて記述します。
|
1 2 3 |
(arg) => { return this; } |
このとき、変換されたJavaScriptは次のようになります。
|
1 2 3 4 |
var _this = this; function(arg){ return _this; } |
thisの参照先が _this に格納され、ラムダ式内の thisは全てここを参照するようになります。
このラムダを使用することで thisの参照先を同一に保つことができ、イベントリスナーや forEach、 sortのコールバックをスリムに記述することができます。
型注釈
冒頭で述べたとおり、型注釈はTypeScriptを特徴づける重要な機能の一つです。
型注釈にて静的型付を行うことで、コンパイラが整合性をチェックし、適合しているかどうかを検査してくれます。
TypeScriptで型注釈を行う場合、次のようにJavaScriptでの変数宣言の後にコロンで区切りその変数の型を指定します。
|
1 |
var value: string; |
このように型注釈をすることで、 valueはstring型と認識されます。ある型で宣言された変数はその型の値しか代入することはできません。以下のコードはJavaScriptであれば何の問題もなく動作します。
|
1 2 3 |
var value; value = "value"; value = 0; |
しかし、この valueがTypeScriptでString型に型注釈されていた場合、 を代入するコードでコンパイルエラーとなります。
|
1 2 3 |
var value: string; value = "value"; value = 0; // コンパイルエラー |
型注釈をすることで、変数に特定の型を持つ値以外が代入できないように、制限することができます。制限を設けることで、存在しないメンバ関数にアクセスしてしまうミスなどを、事前に防ぐことができます。また、静的に検査されるため、型注釈が実行時のパフォーマンスを直接下げることはありません。
TypeScriptには次の基本型があります。型注釈にはこの基本型に加え、関数とオブジェクト、クラス、インターフェースが使用できます。
- number
- string
- boolean
- Array
- Enum
- void
- any
number/string/boolean
number,
string,
booleanはそれぞれJavaScriptの
number,
string,
booleanと対応します。
|
1 2 3 |
var num: number = 0; var str: string = "Hello," + ' World!'; var flag: boolean = false; |
Array
Array型には二つの表記法があります。一つは型の後に []を付ける方法、もう一つが Arrayクラスを利用する方法です。
|
1 2 |
var nums: number[] = [0, 1, 2, 3, 4]; var strs: Array = ["foo", "bar"]; |
Arrayクラスは相称型を利用しています。総称型については次回詳しく紹介します。
Enum
TypeScriptではEnumが導入され、これも型として扱うことができます。
|
1 2 3 |
enum Color {Red, Green, Blue}; // Red=0, Green=1, Blue=2 var color: Color = Color.Green; // 1 var colorName: string = Color[color]; // "Green" |
Enumの値は0から連続する整数値です。また、その値で参照することで文字列を取得することができます。
多くの言語のEnumと同様に値を自分で決めることもできます。
|
1 |
enum Color {Red=1, Green=5, Blue};// Red=1, Green=5, Blue=6 |
値を決める際は代入文を使用します。代入文がない場合は、前の要素の次の値になります。
any
any型は全ての型を指します。型が判断できない場合はこの any型であると判断されます。 any型は全ての型の値を格納でき、また、どの型にもキャストすることができます。
|
1 2 3 4 |
var anyVal: any; anyVal = 0; // ok anyVal = "string"; // ok var num: number = anyVal; // ok |
anyValは
any型なので、数値や文字列などのどの型の値も書くできます。また、どの型にもキャスト可能で暗黙的に変換されます。
上記のコードで
numは
number型を期待していますが、代入されているのは文字列です。しかし、
any型であるためにコンパイルエラーにはなりません。このよう理由から、極力
any型の利用は避けるべきです。
なお、TypeScriptで明示的にキャストするには <> を使用します。
|
1 |
var num: number = anyVal; |
関数とvoid
関数の型を指定することができます。関数の型は、パラメータの型と戻り値の型をしていすることで表記します。
|
1 2 3 4 5 6 7 |
var func: (arg: string) => void; func = function(arg: string): void{ console.log(arg); } func = (arg: string):void => { console.log(arg); } |
このように
(パラメータ名: パラメータの型) => 戻り値の型と表記します。また、
void型は値がないことを表し、関数の戻り値の型注釈のみで利用することが作法となっています。
また、
function(パラメータ名: パラメータの型): 戻り値の型や、
(パラメータ名: パラメータの型):戻り値の型とすることで、関数宣言にパラメータや戻り値の型注釈をすることができます。
デフォルトパラメータは、オプショナルパラメータとしてあらわされます。よって次のコードの defaultFunc関数と optionalFunc関数の型は両方とも同じく (arg?: string) => voidとなります。
|
1 2 3 4 5 |
function defaultFunc(arg: string = "default") {} function optionalFunc(arg?: string){} var func: (arg?: string) => void; func = defaultFunc; // ok func = defaultFunc; // ok |
オブジェクト
オブジェクトも型付けをすることができます。オブジェクトに型付けをすることで、そのオブジェクトにどんなプロパティがあるべきかを定義することができます。
次にコードでは、変数 objはプロパティに messageと stringを持つオブジェクトであることを型付けしています。
|
1 2 3 4 5 6 7 8 |
var obj: { message: string; say: ()=>;void}; obj = { message: "Hello" , say: function( ){ console.log(this.message); } , more: "more" } |
このように、オブジェクトの型付けを利用することで、存在しないプロパティを参照するミスを事前にチェックすることができます。
型推論
型注釈は制約を設け、コンパイラによるチェックを容易にしますが、すべてに注釈すると冗長に見えます。 TypeScriptは他の静的言語と同様に、ある程度型を推論して自動的に型付けをしてくれる型推論があります。
例えば次のコードでは、 strは文字リテラルで初期化しているためstring型であると推論されます。
|
1 2 |
var str = "Hello"; str = 0; // エラー |
関数もある程度推論されます。 次のコードでは、 str関数のパラメータが number型であり、 toStringメンバ関数の戻り値の型は string型であることから、変数 sは string型であると推論されます。
|
1 2 3 4 5 |
function str(num: number){ return num.toString(); } var s = str(100); s = 0; // エラー |
このコードで注釈をしているのは str関数のパラメータの一つだけです。この注釈を外すと numは any型となり、それ以上推論ができなくなり、 str関数の戻り値の型も any型、変数 sの型も any型になります。よって、0の代入はエラーになりません。
便利な型推論ですが、注釈を外し過ぎるとすべてが any型となってしまい、静的型付けの恩恵を得られなくなってしまいます。そこで強くお勧めするのが --noImplicitAnyコンパイルオプションです。このオプションを付けると any型に推論がなされた際に、コンパイルエラーとして通知してくれるようになります。 つまり、このオプションを利用することで、最小限の注釈ですべてがしっかり型付けされることを保証することができます。 しっかり型付けをすることで、コード補完などの支援も受けやすくなるので積極的に使用しましょう。
前回、Sublime Text 3上での開発環境を構築しました。この環境で --noImplicitAnyオプションを有効にするには、「Preferences」 > 「Package Settings」 > 「T3S」 > 「Settings-User」の設定を次のように編集します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "local_tss":true, "error_on_save_only":false, "build_parameters":{ "pre_processing_commands":[], "post_processing_commands":[], "output_dir_path":"none", "concatenate_and_emit_output_file_path":"none", "source_files_root_path":"none", "map_files_root_path":"none", "module_kind":"none", "allow_bool_synonym":false, "allow_import_module_synonym":false, "generate_declaration":false, //この"no_implicit_any_warning"をtrueにする "no_implicit_any_warning":true, "skip_resolution_and_preprocessing":false, "remove_comments_from_output":false, "generate_source_map":false, "ecmascript_target":"ES5" } } |
build_parameters.no_implicit_any_warningプロパティを trueに設定して、上書き保存をすることで、 --noImplicitAnyコンパイルオプションを有効にすることができます。
オーバーロード
JavaScriptは多様性を積極的に活用する言語の一つです。とくにjQueryなどのライブラリでは多くの多様性を持ったメンバー関数が実装されています。
もちろん、TypeScriptでも同様に実装することで多様性は実現できますが、そのままでは
any型になってしまうため、型注釈が必要です。
TypeScriptは関数の実装に対して、シグネチャの異なる複数の宣言を持つことができます。これを利用することで関数のオーバーロードが可能になっています。
次のコードでは render関数をオーバーロードしています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function render(html: string, parent?: HTMLElement); function render(elem: HTMLElement, parent?: HTMLElement); function render(arg: any, parent: HTMLElement = document.body){ if(typeof arg === "string"){ var elem = document.createElement("div"); elem.innerHTML = arg; while (elem.childNodes.length) { parent.appendChild(elem.firstChild); } } else{ parent.appendChild(arg); } } render("<p>Hello, World!</p>"); render(document.createElement("div")); render(0); // コンパイルエラー |
この render関数は (html: string, parent?: HTMLElement)=>void、または、 (elem: HTMLElement, parent?: HTMLElement)=>voidの2つのシグネチャを持ちます。 最後の render(0)は2つのシグネチャのどちらとも一致しないのでコンパイルエラーになります。
まとめ
今回は型注釈を中心に、変数や関数での注釈方法や、関数のオーバーロードなどを紹介しました。また、前回構築した開発環境で --noImplicitAnyコンパイルオプションを有効にし、より、TypeScriptの力を発揮できるようにしました。
型注釈をしっかり施すことによって、型の不一致から発生するバグを実行前に検知し、修正することができます。 これが、TypeScriptの大きな魅力の一つです。 ぜひ、コンパイルオプションを変更して any型がでなるべくないようにコードを書いてみてください。
次回はクラスやインターフェース、総称型を紹介します。
コメント