TypeScript入門―大規模開発に適したJavaScript互換言語

この記事を読むのに必要な時間:およそ 8.5 分

TypeScriptの文法

JavaScriptにも型がありますが,TypeScriptは静的型付け言語ですから,より型を意識したコーディングになります。

型と変数の宣言

TypeScriptは組み込みで次の型を持っています。

  • string:文字列型
  • number:数値型
  • boolean:ブール値(true/false)
  • void:何もない,空からを表す型

ほかにも関数やオブジェクト,nullなどの型もありますが,よく使うのは上記の4つでしょう。

TypeScriptでの変数宣言はJavaScriptと同様varを使います。宣言の変数名の後ろにコロンと型名を書くと変数の型を指定できます。

var text: string;
var count: number;
var enabled: boolean;

変数の型が指定されている場合,その変数に指定した型以外の値を代入しようとするとコンパイルエラーになります。

// 文字列型の変数に数値を代入しようとしてエラー
text = 1;
// 数値型の変数に文字列を代入しようとしてエラー
count = "1";
// ブール型の変数に数値を代入しようとしてエラー
enabled = 0;

しかし,変数を宣言するたびに型を指定するのは面倒です。TypeScriptでは変数宣言で初期値を代入する場合は型の指定を省略できます。たとえば次のようなコードになります。

var text = "hauhau";
var count = 10;
var enabled = true;

一見JavaScriptと同様に見えますね。TypeScriptでは型推論というしくみにより,このコードの変数は暗黙に型が指定された扱いになります。つまり,たとえば初期値として文字列が代入されている場合,コンパイラは「最初に文字列を代入しているということは,この変数の型は文字列型だね」と判断して,その変数を文字列型として扱うのです。当然,型の合わない変数に値を代入しようとするとコンパイルエラーが発生します。

// 文字列型の変数として初期値を持たせつつ定義
var text = "hauhau";
// 文字列型の変数に数値を代入しようとしてエラー
text = 1;

これでまったく異なる型の値を間違えて代入することや混在することがなくなります。気軽に記述しながら静的型付け言語の恩恵を受けられるのです。

関数の定義

TypeScriptでの関数の定義は,基本的にはJavaScriptと同じですリスト2)⁠functionキーワードに続けて関数名を書き,引数を取ってブロックを続けるという形です。JavaScriptとの違いは,引数と関数の戻り値の型を指定できるところです。関数がどのようなデータを受け取ってどのようなデータを返すのかを厳密に指定できます。引数の型指定は変数と同様,引数名の後ろにコロンと型名を書きます。戻り値の型指定は引数リストの後ろにコロンと型名を書きます。

リスト2 関数定義の例

// 引数aとbはnumber型で,戻り値がstring型
function addAndToString(a: number, b: number): string {
    return (a + b).toString();
}
addAndToString('1', '2'); // エラー: パラメータはnumber型しか受け取らない
var value: number = addAndToString(1, 2); // エラー: number型の型にstring型は入ない

JavaScriptには通常の関数定義のほかに匿名関数(無名関数)があります。たとえば次のコードのように,イベントハンドラとして登録するときなどによく利用されます。

document.body.addEventListener('click', function () {
    alert('clicked!');
});

TypeScriptでは,このような無名関数をArrow FunctionというECMAScript 6を先取りした記法で記述できます。Arrow Functionはその名のとおり矢印を使います。先ほどのコードをArrow Functionで書き換えると次のようになります。

document.body.addEventListener('click', () => {
    alert('clicked!');
});

長かったfunctionキーワードがなくなり,代わりに=>が登場しました。functionがなくなったおかげでスッキリしています。

さらに,このArrow Functionにはブロックを取る書き方と取らない書き方の2種類があります。次の2行のコードは,書き方は異なりますが同じ処理です。

[1,2,3].map((v) => { return v * 2; });
[1,2,3].map((v) => v * 2);

ブロックを取る書き方はfunctionを使う定義とほとんど変わりませんが,ブロックを取らない書き方は戻り値を直接書くのでさらにスッキリします。このコードの変換結果を見ると,どちらも同じJavaScriptのコードに変換され,TypeScriptではスッキリ書けていたことがわかります。

[1, 2, 3].map(function (v) {
    return v * 2;
});

なお,Arrow Functionの引数が1つだけのときは括弧を省略できて次のようにも書けます。

[1,2,3].map(v => { return v * 2; });
[1,2,3].map(v => v * 2);

クラスの定義

TypeScriptでクラスを定義するには,ECMAScript 6から先取りして導入されたclassを使います。その際にprototypeベースの開発知識を必要としないのがよいところです。実際にクラスを定義するコードの例を次に示します。

// Greeterクラスを定義
class Greeter {
    // string型のプロパティ
    message: string;
    // パラメータを取るコンストラクタ
    constructor(message: string) {
        this.message = message;
    }
    // メソッド
    sayHello(): void {
        alert('Hello! ' + this.message);
    }
}
new Greeter('Konnichiwa').sayHello();

クラスの定義にはプロパティ,メソッド,コンストラクタを含めることができます。メソッドの定義は関数の定義とほぼ同じ形ですが,functionキーワードは使いません。

定義したクラスを使うにはJavaScriptとまったく同様に,newでインスタンスを生成して利用します。これはJavaScriptから直接扱えるという意味でもあり,TypeScriptでライブラリを作ると,ほかのJavaScriptのコードからもシームレスに呼び出せます。

TypeScriptではメソッドやプロパティにprivate修飾子を付けて宣言すると,クラスの外部から操作ができなくなります。ただし,JavaScript自体にはそのような機構はないので,これはTypeScriptのコードとして処理しているときのみ意味を持ちます。つまり,ほかのJavaScriptのコードからはprivateなメソッドやプロパティにアクセスできてしまうので,注意が必要だということです。

ほかのクラスベースの言語と同じように,TypeScriptでも継承が行えます。次の例では,Greeterクラスを継承したAisatsuクラスを定義して,sayHelloメソッドを上書きしています。

class Aisatsu extends Greeter {
    sayHello(): void {
        alert('Konnichiwa! ' + this.message);
    }
}

ところでコードを書いていると,親クラスとして渡されたものを子クラスとして扱いたい場合が出てきます。たとえば次のコードのような場合です。

// canvas要素を取ってくる
var canvasE = document.querySelector('canvas');
// Canvas 2D Contextを取得する
var ctx = canvasE.getContext('2d');

このコードはJavaScriptとしては問題なく動作しますが,TypeScriptではコンパイルエラーになります。これは厳密に型をチェックしているためです。

まず,document.querySelector('canvas')はドキュメント内のcanvas要素を返しますが,querySelectorメソッドの戻り値は要素を表す汎用的なElement型です(つまり,変数canvasEElement型です)⁠この値に対してgetContextメソッドを呼び出そうとしても,当然Element型にはそのようなメソッドはないのでコンパイルエラーになります。

この場合は型のキャスト(型変換)を行う必要があります。Element型ではなくgetContextメソッドを持つ下位のHTMLCanvasElement型であると認識できればよいのです。キャストするには,リスト3のコードのように<TypeName>を変数や戻り値の前に置きます。このコードでは,document.querySelectorの戻り値をHTMLCanvasElement型にキャストしています。

リスト3 キャストの例

// canvas要素を取ってくる
var canvasE = <HTMLCanvasElement>document.querySelector('canvas');
// Canvas 2D Contextを取得する
var ctx = canvasE.getContext('2d'); // ← OK

キャストのエラーは最終的に実行されたときに発生するので注意が必要です。リスト3のコードでは,canvas要素の実体がまさにHTMLCanvasElement型なので正常に動作しますが,たとえばまったく違う要素div要素など)HTMLCanvasElement型にキャストすると,実行時にどこかでエラーになります。

著者プロフィール

沢渡真雪(さわたりまゆき)

普段はASP.NETやPerlでWebアプリケーションを書くのが主。興味の向きはWindows一般から.NET Framework,Perl(Plaggerとか)やMac OS Xなど。

URLhttp://www.misuzilla.org/