「静的型付けが出来るJavaScriptの上位言語」を触ってみよう。パート3 - TypeScript #3

前々回前回と続けておりますこのシリーズ。 自身の直感を頼りに「触ってみよう」レベルの情報を連載しております。 (コアな情報は記載していません。あしからず。)

今日はTypeScript最終章でいきたいと思います。

本日の見出し

ではいってみましょ

Any

TypeScriptにはanyaは小文字)と言う型があります。

これはなんだと言うと、なんでも型です。

これを指定すると全ての型チェックが無効になります。

え、そもそも型指定する為にTypeScript使うんじゃ...

こちらではこのように記載されています。

any は既存の JavaScript コードを移植するようなときや、JSON 形式のデータを扱うような場合に便利なことがあります。
また、any にキャストするとどんな式でもあらゆる型チェックを回避できるので、どうしてもうまく書けない時の回避策としても重要です。

そう。既存コードなど型を意識しないコードになんでも型を使用することで、柔軟に、簡潔に流用することが出来るような仕組みになっています。

thisの解決

JavaScriptでなにが厄介かと言うとこれです。thisの解決。

使用箇所よってthisの中身は変化する為、いちいち気を使わないといけない。

TypeScriptではどのように解決できるかというと

class Foo {
    greet: string;

    say() {
        console.log(this.greet); // 1.
    }
}

var foo: Foo = new Foo();
foo.greet = 'hello';
foo.say();

こんなクラスがあったとして

  • 「1.」でthisを使用している
  • 「1.」をthis.fooとか存在しないプロパティを指定するとコンパイルエラー(thisがFooクラスとして認識されていることがわかる)

実行すると

$ node usethis.js
hello

もちろん「hello」と自身のプロパティが表示される

ここまではわかる。型を指定出来ないクロージャとかでのthisの扱いが問題。

例えばsetTimeoutとかで指定するコールバック関数内でthisを使用した場合

class Foo {
    greet: string;

    say() {
        setTimeout(function() {
            console.log(this.foo); // 1.
            }, 1000);
    }
}

var foo: Foo = new Foo();
foo.greet = 'hello';
foo.say();

実行すると

$ node usethis.js
undefined

undefinedthisが解決されていない...

こういう場合、TypeScriptは↓のように定義することでthisが解決出来る

class Foo {
    greet: string;

    say() {
        setTimeout(() => {
            console.log(this.greet);
            }, 1000);
    }
}

var foo: Foo = new Foo();
foo.greet = 'hello';
foo.say();
  • 実行すると「hello」が表示される

この() => { ... }のような関数定義をアロー関数と呼ぶらしい。詳しくはこちら

アロー関数のコンパイル済JavaScriptはこんな感じ

...
        var _this = this;
        setTimeout(function () {
            console.log(_this.greet);
        }, 1000);
...
  • _this変数へ退避されている

module(namespace)

TypeScriptmoduleキーワードを使って名前空間(パッケージ)を表現することができる模様

export

moduleキーワードを使って定義した名前空間にクラスを追加するにはexportキーワードを使用する

module utils {
    export class Foo {
        greet: string;

        say() {
            setTimeout(() => {
                console.log(this.greet);
            }, 1000);
        }
    }
}

new utils.Foo();
// new Foo();   // ←これはコンパイルエラー

import

名前空間が長い場合、importキーワードを使用して省略できる

module com.hatenablog.koizuss.utils {
    export class Foo {
        ...
    }
}

import utils = com.hatenablog.koizuss.utils;

new utils.Foo();
  • Javaのようなimport com.hatenablog.koizuss.utils.Fooimport com.hatenablog.koizuss.utils.*のようなインポートはNG

ファイルインポート

exportした別ファイルをインポートすることもできる

import utils = module("utils");

これは↓と等価

var utils = require("./utils");
  • 別ファイルとしてのexportrequireについては前回参照

*.d.ts

宣言ソースファイルというらしい。既存JavaScriptを元にTypeScriptとしての定義のみを書き出したファイル。

宣言ソースファイルを使うことで、例えば自分で実装していない既存JavaScriptライブラリの定義のみを参照し、TypeScriptとして実装できる

  • 参照するだけで使用したライブラリが勝手にインポートされるわけではないので注意

外部ライブラリの参照

有名どころライブラリの宣言ソースファイルはこちら

宣言ソースファイルは↓のようなコメントを記載することで参照できる

/// <reference path="hoge.d.ts" />

宣言ソースファイルのコンパイル

勘違いをしておりまして。過去の記事で「jsファイルからのコンパイル」と書いていましたが、JavaScriptファイルから宣言ソースファイルを生成する方法はありませんでした。すいません。

TypeScriptJavaScriptをライブラリとして展開する場合に、宣言ソースファイルを実装した.tsファイルから生成することができます。

↓のようにコンパイラ--declaration(or -d)オプションを加えてコンパイルすることで宣言ソースファイルを出力できます。

$ tsc --declaration sample.ts
  • sample.tsで実装した型定義がsample.d.tsファイルとして出力されます

IDE

ここまで自身は全てテキストエディタで実装し、コマンドでコンパイルしてきました。

実際にTypeScriptを使ってアプリケーションを作成するのであれば、IDEを使うことが理想的です。

TypeScriptの型解決はIDE上で更に効果を発揮します。

なぜなら、型が定まっていることで、充実したコード補完リアルタイムにコンパイルエラーを確認できるからです。

デモ動画を見てもらうとその効果がよく分かるのではないかと思います。

で、じゃTypeScriptはどんなIDEに対応しているか。

↑の動画で使用されているVisual Studio 2012を初め、以下のエディタを公式にサポートをしています。

公式サポートエディタ

後、IntelliJはプラグインがある模様(JetBrainsすげぇ!!ということはWebStormでも使える?!)


さてさて。

ここまで3回にわたってTypeScriptをざっくり触ってきたんですが、感想をこれまたざっくりとまとめたいと思います。

  • やっぱり型解決できているって健康的
    • アプリケーションは複数のデータと処理を統合することで動きます。他のデータ・処理がどのような責務を持ってどのように振る舞うかを型によって表現し保証することでスムーズな連携を可能とします。 JavaScript上で他の部品を使用する場合の言い知れぬ不安を、静的に型を定義することで事前に解決することができます。 これは精神衛生上かなり健康的!!
  • オブジェクト指向プログラミング(OOP)に適している
    • 素のJavaScriptでもOOPは可能です(若干異論ありそうな気がしますが...)。でもこれじゃない感が拭えません。 やはりOOPは型と言う概念があっての賜物かなと自身は思います。(OOPのメリットは割愛)
  • IDEって素敵やん
    • 型を定義する = 記述するコード量が増えます。 以前に記載した通り、コンパイル済JavaScriptに型は一切反映されません。と言うことは実際動くプログラム以外のコードを書く必要があります。 しかし!!ここでIDEがいい仕事をします。IDEが型を理解することで↑に挙げたような入力補完やコンパイルエラーのリアルタイム検知ができます。 コードを書く量は増えますが、このようなIDE機能を活用することで、実質的にコードをタイプする量は、型解決しないコードを書く場合と大差ないと思われます。 このようなIDEの機能を使用できるのも型と言う概念があってこそです。
  • 柔軟性に優れている

    • TypeScriptとして自身が一番優れていると感じた点はここです。素のJavaScriptも書ける。 例えば、別の処理が使用する振る舞いは、型を意識してきっちり、 振る舞いの中身等、内部に隠蔽されたスコープでは型を意識せず、さくっと、 みたいな部分的な使い分けが可能です。 造るモノ・ヒトに応じたいい塩梅を実現できます。
  • 既存コードがそのまま動作する

    • ↑でも挙げましたがTypeScriptJavaScriptをそのまま記述できます。と言うことは既存資源をそのまま流用できます。 今JavaScriptでなにかしようとした場合、ライブラリを使用しないことはほぼ皆無です。既存のライブラリをそのまま使用できるメリットは非常に大きいと思います。 (使えるというだけで、素のJavaScriptは型解決できません。型を含めた使用は宣言ソースファイルが必要です)

次回

今度は同じノリでHaxeを試してみたいと思います。