Knockout.js を TypeScript で書いてみる


これは [TypeScript Advent Calendar 2014](http://qiita.com/advent-calendar/2014/typescript) の１１日目(12/11)の投稿です。

TypeScirpt、これは単純に優れているのではと思い、この機に試してみました。今回は薄いフレームワーク(ライブラリ)として私が勝手に重宝している [Knockout.js](http://knockoutjs.com/) に TypeScript を適用してみます。

尚、事前準備となる周辺環境（WebStorm、TypeScript本体、TSD）については昨日こちらに書いたので、宜しければ参照ください。
→ [WebStorm で TypeScript を始めよう！](http://qiita.com/hkusu/items/54af02c93fda7996788c)

## まずは Knockout.js の環境構築

Yeoman でサクりと作っちゃいます。

### 今回の前提環境

> yo：`1.3.2`
> generator-webapp：`0.5.1`
> Knockout.js：`3.2.0`
> jQuery：`2.1.1`

### 手順

Yeoman の generator-webapp を起動し、適当に Bootstrap のみを選択します（まあ何でもいいのですが）。

```shell:
$ yo webapp
```

次に、Bower で Knockout.js をインストールします。

```shell:
$ bower install knockoutjs --save
```

このバージョンのジェネレータで作成されるプロジェクトでは、次のコマンドで `index.html` に必要なものが自動で追記されるので、コマンドを叩きます。

```shell:
$ grunt wiredep
```

結果的に、次のように追記されました。jQuery も入っていますね。

```html:index.html
<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/knockoutjs/dist/knockout.js"></script>
<!-- endbower -->
<!-- endbuild -->
```

まずはこの状態(TypeScriptを導入しない状態)で、Knockout.js の動作確認をしてみます。View を次のように書き、

```html:app/index.html
<p>名：<input data-bind="value: firstName, valueUpdate: 'afterkeydown'"></p>
<p>姓：<input data-bind="value: lastName, valueUpdate: 'afterkeydown'"></p>
<h4>フルネームは <span data-bind="text: fullName"> </span></h4>
```

ViewModel を次のように書きます。

```javascript:app/scripts/main.js
$(function() {

  var ViewModel = function(first, last) {
    this.firstName = ko.observable(first);
    this.lastName = ko.observable(last);

    this.fullName = ko.computed(function() {
      return this.lastName() + " " + this.firstName();
    }, this);
  };

  ko.applyBindings(new ViewModel("太郎", "山田"));
});
```

`$ grunt serve` でブラウザを開き、データバインドが動いていればOKです。

## 型定義ファイルの導入

昨日の投稿（[WebStorm で TypeScript を始めよう！](http://qiita.com/hkusu/items/54af02c93fda7996788c)）にも書きましたが、このプロジェクトに jQuery と Knockout.js の型定義ファイルをインストールします。

```shell:
$ tsd init
$ tsd query jquery knockout --action install --resolve --save
```

以降は TypeScript ファイルの冒頭で、次のように型定義ファイルを指定すればOKです。

```typescript
/// <reference path="../../typings/tsd.d.ts" />
```

## Knockout.js を TypeScript で書いてみる

いったん、元の `app/scripts/main.js` は削除し、`app/scripts/main.ts` を作成します。WebStorm のウオッチャーにも追加しておきます。

ここまでで、プロジェクトは次のようになっているハズです。

![スクリーンショット 2014-12-09 19.07.38.png](https://qiita-image-store.s3.amazonaws.com/0/35733/a44b7733-5af6-e3d8-004d-59a2aac53bf7.png "スクリーンショット 2014-12-09 19.07.38.png")

### View を書きます

こちらは普通に。

```html:app/index.html
<p>名前：<input data-bind="value: name, valueUpdate: 'afterkeydown'"></p>
<p>年齢：<input data-bind="value: age, valueUpdate: 'afterkeydown'"></p>
<p>あなたは <span data-bind="text: name"></span> さん、<span data-bind="text: age"></span> 歳 ですね！</p>
```

### ViewModel を書きます

あまり TypeScript 分かってないので.. それっぽく書きます。`name` は文字列型(string)、`age` は数値型(number) という風に型を定義しました。

```typescript:app/scripts/main.ts
/// <reference path="../../typings/tsd.d.ts" />

class HogeViewModel {
  // 何も指定しないと public になる
  private name: KnockoutObservable<string> = ko.observable('');
  private age: KnockoutObservable<number> = ko.observable(0);

  constructor(name: string, age: number) {
    this.name(name);
    this.age(age);
  }
}

$(function() {
  ko.applyBindings(new HogeViewModel('山田', 32));
});
```

コンソールで `$ grunt serve` し動作確認します。

![スクリーンショット 2014-12-09 19.19.53.png](https://qiita-image-store.s3.amazonaws.com/0/35733/82851998-b26f-90a8-bea4-25a1a7411505.png "スクリーンショット 2014-12-09 19.19.53.png")

## 型チェックやコード補完の具合を見てみる

※ 上にも書きましたが、IDE は WebStorm を使っています。

Knockout.js のコード補完が効いています。

![4.png](https://qiita-image-store.s3.amazonaws.com/0/35733/f460c6c2-d189-6418-1b6c-383d6dd9e78c.png "4.png")

![7.png](https://qiita-image-store.s3.amazonaws.com/0/35733/8abd0b6d-984e-9a28-4194-5eac29cb0edd.png "7.png")

もちろん、TypeScript のコード補完も効きます。

![6.png](https://qiita-image-store.s3.amazonaws.com/0/35733/3b67ef90-1372-0836-4b63-80cc3f40fc71.png "6.png")

型エラーは、リアルタイムにチェックされます。

![b87696ed-a096-718d-96cb-bb01b04cb4f4.png](https://qiita-image-store.s3.amazonaws.com/0/35733/38d36b8e-a729-0bfa-51b0-c5546d2c091b.png "b87696ed-a096-718d-96cb-bb01b04cb4f4.png")

![3cfc22b2-e6ea-b294-a948-111846aa6853.png](https://qiita-image-store.s3.amazonaws.com/0/35733/1990a5c4-4c3a-a524-e909-7697439dbbc3.png "3cfc22b2-e6ea-b294-a948-111846aa6853.png")

ちなみに型定義ファイルを指定しないと、こんな感じでエラーになります。

![5.png](https://qiita-image-store.s3.amazonaws.com/0/35733/94eea470-0184-905b-4356-ffb2f4a5dae5.png "5.png")

## コンパイル後の `.js` ファイルを見てみる

こんな感じで変換されています。綺麗に読める感じ。

> ちなみに型チェックがある JavaScript コードが吐かれるわけではないです。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/35733/4dff0c90-731f-be83-6291-600cb6d36b22.png "1.png")

# おわりに

まだ導入の域を出ないですが、Knockout.js を TypeScript で書く手順を紹介しました。
TypeScript の静的な型システムは強力なので、これから自分的に試していきたいな、と思っています。

今回のソースはこちらに置きました。
→ https://github.com/hkusu/Knockout.js_TypeScript_sample
（`.js`ファイルや型定義ファイルはもしかして commit しない流儀なんだろうか？ よくわからず。）