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 にて確認したところ上記のように改善されていました!