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

最近JavaScriptを書くことが多い

JavaScriptは書けば書くほどモヤモヤする(原因のほとんどは自身の力不足だが...)

今後どんどん触れる機会を増すJavaScript達をもっとスッキリ書けないものかと、

考えた結果、やはり型の表現が難しいことが理由のひとつかと思ったりした。

そこで!!!

「静的型付けが出来るJavaScriptの上位言語」を触ってみよう。

今日はその第一弾

なにやんの??

探すといろいろある

どれがいいとかはわからないので、とりあえずちょろりと書いて感覚を味わってみようと思う。

(このシリーズはあくまで導入部分のみを記載、詳細な部分にはふれません。あくまで自身の感覚的なお話)

対象

「静的型付けが出来るJavaScriptの上位言語」で自身が興味のある以下3つを選択

Dartが一番興味があるのだが、Dartの良し悪しを測る為にも他の言語を触れておく Dartについては後ほど

環境

  • OS: Max OS X(10.7.5)
  • Editor: Sublime Text 2
    • おきになのでww
    • 本来IDEを使う方がサクサクなんだろうけど、一旦素のEditorで書いてみる

じゃまずはTypeScriptから

TypeScript

http://www.typescriptlang.org/

ざっくりと

  • Microsoft製
    • IDEはVisualStudioがいい感じ??
    • MS系言語のクライアント実装とかに使うと捗りそう
  • 新手
    • 2012〜
  • ソースファイル(.ts)をコンパイルしてJavaScriptを出力
    • コンパイルする時に型を判定
    • 同じアプローチの言語としてCoffeeScriptとよく比較されてる
  • 既存コードとの親和性が高い
    • 使用されるデータ型JavaScriptと同じなので既存コードと親和性が高い
    • JQueryやBackbone.jsなどの既出ライブラリも合わせ使用可能

かなり詳しく載っているので詳細はこちら

TypeScriptこの動画を元に試してみる

本日の見出し

コンパイラインストール

まずはコンパイラがないと何も出来ないのでインストール

$ npm install -g typescript

これだけ。簡単ですねww

  • npm(node)のインストールはmacなら brew install nodeとか
$ tsc -v
Version 0.8.3.0

コンパイルしてみる

まずはsample.tsをつくって、普通にJavaScriptで関数を書いてみる

function proccess(x) {
    x.name = 'foo';
    var v = x + y;
    console.log(v);
}

このままコンパイルしてみる

$ tsc sample.ts
.../sample.ts(3,16): The name 'y' does not exist in the current scope

yがないっておこられちゃいましたね。

動画のコードにだまされたww

ここでわかるのは素のJavaScriptコードもコンパイルできると言うこと

JavaScriptのバリデータとしても使えるし、JavaScriptコードも混ぜて実装したり、部分的にTypeScriptに移行なんかもできそう

コンパイルが通ると同じディレクトリにsample.jsができる

  • まだ普通のJavaScriptコードしか書いてないのでsample.tssample.jsは全く同じ内容

引数型

そろそろ本題

関数の引数に型を定義をできる

引数xstring型で定義

function proccess(x: string) {
    x.name = 'foo';
...

こういうコードをコンパイルすると

$ tsc sample.ts
.../sample.ts(3,6): The property 'name' does not exist on value of type 'string'

ですね。x.name = 'foo';が怒られますね。xstringとして定義したため、stringnameプロパティがないと

しっかりプロパティ判定が行われてます

動画で紹介されていたxnumberや、boolで定義した場合はコンパイル通っちゃったので割愛(IDEなら警告がでる??)

動画でさらっと出て来ましたが、戻り値型を持つ関数を引数として受け取る引数は以下のような定義のよう

function proccess(x: () => string) {
    x().charAt;
}
  • 関数を受け取り実行
  • その関数が戻り値型stringで定義されている為、charAtが使用出来る

型定義されたプロパティを持つオブジェクトを引数型とすることもできる

function proccess(x: {a: number; b: string;}) {
    x.b.charAt;
}
  • プロパティはそれぞれセミコロンが必要(無いとコンパイルエラー、忘れそう...カンマ書きそう...)
  • もちろんcharAtの対象オブジェクトがxでもx.aでもコンパイルエラー

引数のオプション化

↑のコードを実行する場合

proccess({a: 1, b: 'sample'});

となる

でもaは必要ないので省略したい

proccess({b: 'sample'});

これはコンパイルエラー

そこで

function proccess(x: {a?: number; b: string;}) {
    x.b.charAt;
}

引数名に?をつけるとオプション化することができる

引数オブジェクトをインターフェースとして抽出

{a?: number; b: string;}

この部分をインターフェースとして抽出し、再利用できる

interface Thing {
    a?: number;
    b: string;
}

function proccess(x: Thing) {
    return x.b.charAt;
}

こんな感じ、かなりスッキリですね


ここまでコードをコンパイルしたJavaScriptコードは全て引数型定義がなくなったもの

もちろんですがコンパイル後のJavaScriptでは型は無効となる

コンパイル後のJavaScript

function proccess(x) {
    return x.b.charAt;
}

クラス定義

さてさて、じゃクラスを書いてみる

クラスの定義はこんな感じ

class Point {
    x: number;                              // ... 1. プロパティ
    y: number;

    constructor(x: number, y: number) {     // ... 2. コンストラクタ
        this.x = x;
        this.y = y;
    }

    static origin = new Point(0, 0);        // ... 3. スタティックメンバー

    func1() { return this.x; }              // ... 4. メソッド
    private func2() { return this.y; }      // ... 5. アクセス制御
    get func3() { return 'sample'; }        // ... 6. アクセッサ

}

ざっくりと

  1. プロパティ
    • 型が指定できる
  2. コンストラクタ
  3. スタティックメンバー
    • 上記であればPoint.originのように使用
  4. メソッド
    • new Point().func1()
  5. アクセス制御
    • new Point().func2()はコンパイルエラー
  6. アクセッサ
    • new Point().func3のように使う
    • アクセッサを定義する場合はコンパイル時に--target ES5が必要
      • tsc --target ES5 sample.ts
      • デフォルトはES3でコンパイルされる

ゆるくなく、ガチガチでもなく程よい

全くもってしっくりくる!!!

コンパイルすると

var Point = (function () {
    function Point() { }
    Point.prototype.initialize = function (x, y) {
        this.x = x;
        this.y = y;
    };
    Point.origin = new Point(0, 0);
    Point.prototype.func1 = function () {
    };
    Point.prototype.func2 = function () {
    };
    Object.defineProperty(Point.prototype, "func3", {
        get: function () {
            return 'sample';
        },
        enumerable: true,
        configurable: true
    });
    return Point;
})();

アクセッサの部分以外想像通り

アクセス制御はこれまたtsコードの中だけで有効(コンパイルすると消える)

このコードをJavaScriptで書くストレスを考えると、これだけでTypeScriptを使う価値がある

継承

class Point3D extends Point {

    constructor(x: number, y: number, z: number) {
        super(x, y);
    }

    func1() { return this.y }
}

自然だ。

コンパイルすると

var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Point = (function () {
    ...
})();
var Point3D = (function (_super) {
    __extends(Point3D, _super);
    function Point3D(x, y, z) {
        _super.call(this, x, y);
    }
    Point3D.prototype.func1 = function () {
        return this.y;
    };
    return Point3D;
})(Point);

当たり前ですが、ちゃんと継承が実装されてる

継承すると__extendsメソッドが自動生成される

  • 継承を全く使ってなければ__extendsは生成されない
  • Backbone.jsなんかはUnderscoer.jsの_.extendを使うけど、この場合はextendsは使えない??(これは後半で記載します)

んんん、なるほど、もはやこれだけでも素晴らしと思ってしまった。

こっからが面白いんですが、情報量が多くなってしまったので今日はここまで。

次回「TypeScript後半戦」として以下のようなことを試してみます。

  • Any
  • thisの解決
  • module(namespace)
    • export
    • import
    • ファイルインポート
  • *.d.ts
    • jsファイルからのコンパイル
  • IDE
本日参考にさせていただいたサイト

ありがとうございました。