Knockout.js 日本語ドキュメント

TypeScript で Knockout を使ってみる

TypeScript という言語を使って、Knockout のサンプルコードを書いてみました。

TypeScript は CoffeeScript などと同様に JavaScript へコンパイルするタイプの言語で、C# に近いオブジェクト指向が特徴です。 ここでは、Knockout, jQuery, linq.js を組み合わせて使うシンプルな例を示します。
(linq.js を用いたコレクション操作については、 neue.cc 先生のライブコーディングパク参考にさせて頂きました。)

デモ

Twitter キーワード検索

検索キーワード: ("test"と入力すると比較的多くの言語のツイートがヒットします)
ヒットしたツイート:
使用言語:

コード: View

<h1>Twitter キーワード検索</h1>

検索キーワード: ("test"と入力すると比較的多くの言語のツイートがヒットします)
<form data-bind="submit: search">
	<input type="text" data-bind="value: searchKeyword, valueUpdate: 'afterkeydown'" placeholder="検索キーワード"/>
	<button type="submit">SEARCH</button>
</form>

ヒットしたツイート:
<div data-bind="foreach: searchResult">
	<div class="tweet">
		<span class="username" data-bind="text: '@' + from_user"> </span>
		<span class="tweet_text" data-bind="text: text"> </span>
	</div>
</div>

使用言語:
<div data-bind="foreach: tweetLanguages">
	<span class="tweet_lang" data-bind="text: $data"> </span>
</div>

コード: ViewModel (TypeScript)

// 外部参照
/// <reference path="Scripts/jquery.d.ts" />
/// <reference path="Scripts/knockout.d.ts" />
/// <reference path="Scripts/linq.js.d.ts" />

// ツイッタークライアント(モデル)定義
class TwitterClient {
    static search(keyword: string, callback?: (json: any) => any) {
        $.getJSON("http://search.twitter.com/search.json?callback=?", { q: keyword }, callback);
    }
}

// メイン ViewModel 定義
class AppViewModel {

    searchKeyword: string = ""; // 検索キーワード
    searchResult: KnockoutObservableArray = ko.observableArray([]);	// 検索結果配列
    tweetLanguages: KnockoutComputed; // 検索結果のツイートの使用言語ごとの集計

    constructor() {
		// 検索結果から使用言語ごとの集計を得る LINQ クエリを ko.computed で使用
        this.tweetLanguages = ko.computed(() =>
            Enumerable.from(this.searchResult())
                .groupBy(t => t.iso_language_code) // 言語でグループ化
                .select(g => g.key() + " (" + g.count() + ")") // 文字列として射影
                .toArray() // 配列に変換
        );
    }

    search() { // 検索アクション
        TwitterClient.search(this.searchKeyword, (json) => {
            this.searchResult(json.results);
        });
    }
}

$(() => ko.applyBindings(new AppViewModel()));

著者雑記

TypeScript は JavaScript の上に、純粋なオブジェクト指向, 型推論, ラムダ式などのシンタックスシュガーを構築する とてもパワフルな言語です。しかし、このサンプルを書いた時に1つだけ疑問に思った点があります。

それは、this が使えないコンテキストについてです。TypeScript において、コンパイラレベルで this が使えないコンテキストがあります。

TypeScript「this」が使えるコンテキスト

  • グローバルコンテキスト, 通常の関数定義および function
  • クラスのコンストラクタ, メンバ関数およびメンバアクセサ

上記2つ以外のコンテキストでは、this を使おうとするとコンパイルエラーになります。 たとえば、このサンプルの AppViewModel.tweetLanguages を、定義と同時に ko.computed で初期化しようとすると、内部で参照している this.searchResult() にてコンパイルエラーが発生してしまいます。 つまり、しかたなくコンストラクタの内部で初期化しているのです。

この挙動は、JavaScript のオブジェクトリテラルについて理解されている方は 「そりゃそうだろう」と思われるかもしれません。 そこで、次のコードを見て下さい。上記の TypeScript コードは、次のようにコンパイルされます。

コード:ViewModel (コンパイル結果の JavaScript)

		var TwitterClient = (function () {
			function TwitterClient() { }
			TwitterClient.search = function search(keyword, callback) {
				$.getJSON("http://search.twitter.com/search.json?callback=?", {
					q: keyword
				}, callback);
			};
			return TwitterClient;
		})();
		var AppViewModel = (function () {
			function AppViewModel() { // プロパティもコンストラクタで生成してるじゃん!
				var _this = this;
				this.searchResult = ko.observableArray([]);
				this.searchKeyword = "";
				this.tweetLanguages = ko.computed(function () {
					return Enumerable.from(_this.searchResult()).groupBy(function (t) {
						return t.iso_language_code;
					}).select(function (g) {
						return g.key() + " (" + g.count() + ")";
					}).toArray();
				});
			}
			AppViewModel.prototype.search = function () {
				var _this = this;
				TwitterClient.search(this.searchKeyword, function (json) {
					_this.searchResult(json.results);
				});
			};
			return AppViewModel;
		})();
		$(function () {
			ko.applyBindings(new AppViewModel());
		});

コメントにも書いたとおり、すべてのプロパティはコンストラクタの内部で宣言・初期化されます。 つまり、コンストラクタの先頭で宣言している var _this = this; が使えるスコープにあるわけです。 それならば、プロパティ宣言の初期化処理で this が使えたっていいのではないか、と思うのです。

TypeScript はまだ正式リリースされていないので、どうにか this を使える子にしてあげて欲しいです。

追記: TypeScript 0.9.5 にて確認したところ上記のように改善されていました!

この記事は、翻訳者が独自に作成したものです。

side menu