TypeScriptを触ってみた。

nakamuraが2015/08/19 18:41:54に投稿

TypeScriptを触ってみた

TypeScriptとは

TypeScriptはJavaScriptに静的な型付けやクラスベースのオブジェクト指向などの機能を加えたプログラミング言語で、
コンパイルすることでJavaScriptに変換することができます。JavaScript(ECMAScript 5)の上位互換になっています。

ライセンスはApache License 2.0でMicrosoftによって開発されています。
開発はTurbo PascalやDelphi、C#などを開発したことで知られるアンダース・ヘルスバーグ (Anders Hejlsberg)氏が主導しているそうです。

Googleが開発するフレームワークAugularの次期バージョンでもTypeScriptの採用が決まっており、
AltJSの中でも広く使われることになるように思います。

特長

静的型付け

JavaScriptでは変数にどんな型の値でも代入できますが、
TypeScriptでは宣言時に指定した型以外を代入しようとするとコンパイル時にエラーとなります。
これによって単純なミスをかなり減らすことができます。

型推論

静的型付けは便利ですが、変数宣言の際にいちいち型を指定するのは面倒です。
TypeScriptでは型推論をサポートしているため、ある程度型の指定を省略できます。

var s = 'Hello, world!';
// → sは文字列型(string)

ES6への対応

今年の6月17日にJavaScriptの最新標準仕様ECMAScript 6(ES6またはECMAScript 2015)が採択されました。
ES6ではクラスや関数のデフォルト引数など多くの機能が追加されました。しかし、
https://kangax.github.io/compat-table/es6/
を見るとブラウザで使えるのはまだ一部の機能に限られます。
TypeScriptではES6の機能が数多く取り入れられていて、すぐにES6の新機能が使えます。

IDEでのサポート

TypeScriptはVisual Studio 2013/2015やVisual Studio CodeなどのMicrosoftのIDEだけでなく、
AtomやBrackets、Sublime Text、Emacs、Vimなど多くのIDEやエディタでサポートされているので、
自分の好きなものが使えます。

生成されるJavaScriptの質

TypeScriptはScala.jsなどと違いコンパイル後のJavaScriptが普通の人でも読めるものになっています。
例えばTypeScriptで

console.log('Hello, world!');

と書いてコンパイルすれば生成されるJavaScriptは全く同じものです。
TypeScriptはSource Mapをサポートしていますが、それを使わなくても
デバッグは簡単にできるでしょう。またあとでTypeScriptの使用をやめたとしても
きれいなJavaScriptが残りますから、嫌になったらいつでもTypeScriptをやめられます。

JavaScriptとの親和性

既存のJavaScriptのライブラリをTypeScriptから使うには、型定義ファイル(Cのヘッダーファイルみたいなもの)が必要ですが、
DefinitelyTypedにNode.jsやjQueryなどたくさん登録されています。
既に1000以上の型定義ファイルがコミットされているようなので、大抵のものはあると思います。
型定義ファイルがあればTypeScriptからJavaScriptの関数を呼ぶときなどに、コードの補完もできます。

またTSD (TypeScript Definition manager for DefinitelyTyped)を使えば、
DefinitelyTypedから必要なライブラリの型定義ファイルを検索したり、インストールしたりといったことが
コマンドライン上で簡単にできます。

いまいちなところ

TypeScriptはJavaScriptの上位互換となっていて、機能の追加はありますが、変更はありません。
そのためJavaScriptのだめな仕様はそのまま引き継がれています。
例えば==は(CoffeeScriptと違い)TypeScriptでもJavaScriptと同様、自動的に型を変換して比較を行いますので、
2 == '2'trueになる、とかいろいろ気を付けないと行けないところがあります。

またTypeScriptはCoffeeScriptなどに比べてコンパイル時間が長いようです。
型推論に時間がかかっているのかもしれません。

TypeScriptチュートリアルをやってみる

インストール

WindowsならVisual Studio用のExtensionを使ってもいいですが、
他の環境と合わせてnpm(Node Package Manager)でインストールしました。

> npm install -g typescript

最初のサンプル

greeter.tsを以下のような内容で作成します。
チュートリアルのソースではブラウザの画面にメッセージが表示されなかったので少し修正しました。

function greeter(person) {
    return "Hello, " + person;
}

var user = "Jane User";

window.addEventListener('load', function() {
    document.body.innerHTML = greeter(user);
});

JavaScriptにコンパイルするには以下を実行します。

> tsc greeter.ts

これでgreeter.jsが作成されます。作成されたJavaScriptはスペースの数など以外、
もとのTypeScriptのソースとまったく同じでした。

次にgreeter.htmlを

<!DOCTYPE html>
<html>
<head>
  <title>TypeScript</title>
  <script src="./greeter.js"></script>
</head>
<body>
</body>
</html>

という内容で作成しブラウザで開けば、以下のように表示されます。

TypeScript

型の付いたサンプル

上のサンプルはただのJavaScriptでした。関数の引数に型を付けてみます。

function greeter(person: string) {
    return "Hello, " + person;
}

var user = "Jane User";  // 型推論があるのでuser: stringと書かなくてもstring型となる

window.addEventListener('load', function() {
    document.body.innerHTML = greeter(user);
});

Scalaと同様に型は変数の後ろに「:(コロン)」に続けて書きます。
これをコンパイルすると、「: string」が取り除かれて、上の例とまったく同じJavaScriptが生成されます。

今の例では関数greeterの引数はstringと宣言しているため、文字列以外を渡すとエラーになります。
例えば

function greeter(person: string) {
    return "Hello, " + person;
}

var user = ["Jane User"];  // userはstring型ではなくstring[]型

window.addEventListener('load', function() {
    document.body.innerHTML = greeter(user);
});

のように文字列ではなく文字列の配列を渡してしまうと、

> tsc greeter.ts 
greeter.ts(8,39): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'string'.

というエラーとなります。

最初の例では型が指定されていないのでどのような変数を渡してもエラーにはなりません。
これは型としてanyを指定するのと同じです。

型を宣言しなかった場合でも、引数の数が違えばエラーになります。

インターフェース

インターフェースを使うとJavaScriptのオブジェクトがどんなフィールドやメソッドを持つかを決められます。
以下の例ではインターフェースPersonが2つのフィールドを持っています。

interface Person {
    firstname: string;
    lastname: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstname + " " + person.lastname;
}

var user = {firstname: "Jane", lastname: "User"};

window.addEventListener('load', function() {
    document.body.innerHTML = greeter(user);
});

変数userは型を指定していないため、型推論によって{firstname: string, lastname: string}型となりPerson型にはなりません。
型が違うのにgreeter関数に渡してコンパイルエラーにならないのは、user変数の型と
Person型の持つ必要のあるフィールドをすべて持っているためです。つまりTypeScriptでは型の名前ではなく実際にその中身を見ています。
Scalaとかにある構造的部分型とかいうやつです、多分。

クラス

TypeScriptはES6で導入されたクラスをサポートしています。
Javaなどとはちょっと違うので慣れないと不思議な感じです。
TypeScriptではクラスのコンストラクタは(クラス名ではなく)constructorという名前の関数です。

class Student {
    fullname: string;
    constructor(public firstname: string, public middleinitial: string, public lastname: string) {
        this.fullname = firstname + ' ' + middleinitial + ' ' + lastname;
    }
}

interface Person {
    firstname: string;
    lastname: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstname + " " + person.lastname;
}

var user = new Student('Jane', 'M.', 'User');

window.addEventListener('load', function() {
    document.body.innerHTML = greeter(user);
});

インターフェースPersonと関数greeterは今までどおりです。
新しくStudentクラスを追加しました。このクラスはコンストラクタと
4つのパブリックなフィールドfullname, firstname, middleinitial, lastnameを持ちます。
Javaと違っていて分かりづらいですが、コンストラクタの引数にpublicと書いてやると

class Student {
    fullname: string;
    firstname: string;
    middleinitial: string;
    lastname: string;
    constructor(firstname: string, middleinitial: string, lastname: string) {
        this.fullname = firstname + ' ' + middleinitial + ' ' + lastname;
        this.firstname = firstname;
        this.middleinitial = middleinitial;
        this.lastname = lastname;
    }
}

と書いたのと同じになります。

TypeScriptを触ってみた感想

TypeScriptはGoogleでも使われているので、今後さらに普及していきそうです。
今まではRailsなどではCoffeeScriptがよく使われていましたが、
ある程度規模が大きくなると型のないCoffeeScriptは非常につらいです。
そういったところは今後TypeScriptに置き換わっていくのだと思います。

通常のJavaScriptはそのまま正しいTypeScriptになるようなので、
すでにJavaScriptのコードがある場合でも使い始めやすいのもいいと思います。

またTypeScriptはいろいろなIDEによってサポートされています。
CoffeeScriptでコード補完などに対応したIDEはあまりないと思いますが、
TypeScriptならVisual StudioからVimまでいろいろあるので自分の好きなものが使えます。

AltJSと呼ばれるものにはいろいろなものがありますが、以上のような理由から
現状ではTypeScriptを採用しておけば間違いないように思います。