しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

knockout.js の注意すべき点

相変わらず knockout.js でクライアントサイドコードを全力で書いてる男です。

最近、ちょっとした点でデバッグに時間をかけてしまったり、無駄なコードを書いてしまうことがあったので忘れないためにメモしておきます。

値の取得、設定は関数呼び出し

もう基本中の基本なんですが割と間違えます。例えば以下のようなコードを書いてしまったり。

// VM を作成
var viewModel = {
    name: ko.observable(),
    items: ko.observableArray()
};

// name は ko.observable なので NG
alert(viewModel.name);

// 同じく ko.observable なので NG
viewModel.name = "ほげほげ";

// items は ko.observableArray なので NG
alert(viewModel.items[0]);

// 同じく ko.observableArray なので NG
viewModel.items[0] = "ほげほげ";

ついついやってしまって動かないと悩むことになります。正しくは以下のように書きます。

// VM を作成
var viewModel = {
    name: ko.observable(),
    items: ko.observableArray()
};

// name を関数として呼び出す
alert(viewModel.name());

// 同じく引数を与えて呼び出す
viewModel.name("ほげほげ");

// items を関数として呼び出して配列を取得
alert(viewModel.items()[0]);

// 同じく関数として呼び出して配列に値を設定
viewModel.items()[0] = "ほげほげ";

完全にプロパティとして動作してくれたら最高に便利なんですが、相変わらず古い IE がネックらしいです。

バインド式の内部で条件式を使う場合

visible バインディングや if バインディングでは条件式を指定することが出来ます。普通に考えると使えないとおかしいですよね。

しかし、ここにさっきと同様の罠があります。例えば以下のように書いてしまうと正しく動きません。

<!-- value が 10 以上の場合だけ表示したい -->
<!-- ko: if value > 10 -->
<span>OK</span>
<!-- /ko -->

何故動かないのか想像がついた人もいると思います。そうです、条件式を使う場合には関数として呼び出して値を取得する必要があるからです。

例えば value が bool 値の場合は

<!-- ko: if value -->
<span>OK</span>
<!-- /ko -->

だけで正しく動作するので混乱してしまいがちです。主に私が。

なので正しくは以下のように書きます。

<!-- value が 10 以上の場合だけ表示する -->
<!-- ko: if value() > 10 -->
<span>OK</span>
<!-- /ko -->

これで期待した通りの挙動になってくれます。

knockout.js でテンプレートを動的に切り替える

こんばんは、最近は knockout.js のことばかり弄っていて、そろそろ ASP.NET MVC のことも書けよと言われそうですが、MVC 4 には knockout.js 標準で入ってるし問題ないです(免罪符

相変わらずお仕事で knockout.js 2.0.0 を使いまくっているので、やたらとノウハウが蓄積されていってます。やっぱり仕事として弄ると、逃げられないので良いですね(白目 なので、ASP.NET MVC でのお仕事やってみたいなーと思ってますが、それは転職しないと叶いそうありません。

まあ、そのあたりはどうでもいいとして、今日はテンプレートを動的に切り替えてみたいと思います。

knockout.js では if/ifnot などのフローを使って、HTML の出し分けというのは可能です。例えば以下のようなコードです。

<!-- items の中身を出力 -->
<!-- ko template: { name: "sample", foreach: items } --><!-- /ko -->

<script id="sample" type="text/html">
<!-- ko if: type == 1 -->
<p>type == 1 の時のテンプレート</p>
<!-- /ko -->

<!-- ko if: type == 2 -->
<p>type == 2 の時のテンプレート</p>
<!-- /ko -->
</script>

sample テンプレートの中で if を使って HTML の出し分けを行っています。テンプレートが小規模な時などはこれで十分な場合もあります。

しかし、1 つの巨大なテンプレートを作ってしまうと最新のブラウザでも目に見えて DOM 構築に時間がかかるようになってしまいます。knockout.js は内部で DOM 全体のクローンを作ってからいろいろとやっているのかもしれません。

このように大規模なテンプレートを出し分けしたい場合には、テンプレート自体をそれぞれに用意して出しわける方法を使いましょう。先にサンプルコードを出しておきます。

<!-- items の中身を出力 -->
<!-- ko template: { name: templateSelector, foreach: items } --><!-- /ko -->

<script id="sample_1" type="text/html">
<p>type == 1 の時のテンプレート</p>
</script>

<script id="sample_2" type="text/html">
<p>type == 2 の時のテンプレート</p>
</script>

<script type="text/javascript">
var viewModel = {
    items: ko.observableArray()
};

// データから使用するテンプレート名を決定する
viewModel.templateSelector = function (item) {
    if (item.type == 1) {
        return "sample_1";
    } else if (item.type == 2) {
        return "sample_2";
    }
};

// とりあえずダミーデータをセット
viewModel.items.push({ type: 1 });
viewModel.items.push({ type: 2 });

ko.applyBindings(viewModel);
</script>

変わった点は個別のテンプレートとして分離したこと、そして template バインディングの name に templateSelector という関数を設定しているところです。

name に関数を指定すると、配列要素のレンダリングごとに呼び出され、要素の情報から使用するテンプレート名を決定することが出来ます。WPF/Silverlight で言うところの DataTemplateSelector に該当する機能だと思ってください。

そして今までは大きな sample テンプレートを使っていたので、レンダリングに時間がかかる場合がありましたが、個別のテンプレートとして分離したことで大幅な高速化も実現できました。ちなみに私は個別のテンプレートを別ファイルにしておいて、Smarty の include を使って読み込むようにしています。

knockout.js 2.0.0 がリリースされたので試してみた

screenshot

1.3.0 のベータかと思ったら 2.0.0 の RC がリリースされていましたが、ついに 2.0.0 の正式版がリリースされたようです。

NuGet Gallery | knockoutjs 2.3.0

NuGet でも 2.0.0 が配布されているので、急いで以下のコマンドを Package Manager Console に打ち込みましょう。

Install-Package knockoutjs

まずは作者である Steven Sandarson さんのブログエントリを確認しておきましょう。

Knockout 2.0.0 released - Steve Sanderson’s blog - As seen on YouTube™

ちなみに 1.3.0 から 2.0.0 になった理由もしっかりと説明されていますね。

Why is it called 2.0.0? Why not 1.3.0?
For a long time, we were planning this next version to be called 1.3.0. However,

  • Quite a few community members are keen on adopting SemVer-style versioning. 2.0.0 is a good place to start (expermentally) with that versioning convention.
  • It’s such a big set of core changes that if this doesn’t count as 2.0, I guess nothing ever would…

1.3.0 はコア部分に大きな変更が行われたこと。セマンティックなバージョン管理を求められていたので、それなら 2.0.0 から始めたら綺麗だよね。ということみたいです(自信なし

そしてメインである New Features を確認しておきましょ。全て紹介は難しいので、今回はよく使うであろう機能だけ紹介したいと思います。

  1. Control flow bindings
  2. Containerless control flow
  3. Access to parent binding contexts
  4. Cleaner event handling
  5. Binding providers (and hence external bindings)
  6. Throttling

ここには書かれていないですが、超重要な点としてテンプレートエンジンが内蔵されたことがあげられます。jQuery Templates は残念ながら時代遅れな存在になりつつあります…。

Control flow bindings

新しいバインディングとして foreach, if, ifnot, with という 4 つが追加されています。まずは foreach と if を使ってみます。

<ul data-bind="foreach: items">
    <li><span data-bind="text: name"></span><span data-bind="if: new_flg"> - NEW</span></li>
</ul>

<script type="text/javascript">
var viewModel = {
    items: ko.observableArray([
        { name: "はうはう", new_flg: false },
        { name: "ほげほげ", new_flg: true }
    ])
};

ko.applyBindings(viewModel);
</script>

サンプルはこちら → http://jsfiddle.net/shibayan/Zm7bK/

items の中に入ってる値をリストとして表示するだけの簡単なコードですが、テンプレートを使っているようには見えませんね。data-bind で foreach を指定した場合には子要素がテンプレートとして扱われるので、普通の HTML と同じように書くことが出来、可読性も上がっています。

そして span に if を付けていますが、これは new_flg が true の時にその要素の中身を出力するという機能です。visible と変わらないんじゃ?と思われる方もいると思いますが、出力された DOM が異なってきます。

if の値が false の場合はテキストノードが生成されていないことがお分かり頂けると思います。visible では display: none を指定するだけなので要素の中身は保持されるので、挙動が決定的に異なっています。

注意点としては、data-bind 属性を指定している要素自体は残るので、ボーダーなどのスタイルを当てている場合は表示されてしまいます。要素ごと出力したくない場合には、次で紹介する Containerless control flow を使います。

残った with ですが、これはコンテキストを切り替える機能を持っています。例えば 2 つ以上の小さなビューを持つ場合は、ViewModel の中に別々の ViewModel を持たせたりすると思います。

with を使うと以下のように綺麗に書くことが出来ます。ネタが思いつかなかったので Twitter クライアント風味です。

<div data-bind="with: home">
    <h2 data-bind="text: name"></h2>
    <ul data-bind="foreach: tweets">
        <h3 data-bind="text: name"></h3>
        <p data-bind="text: text"></p>
    </ul>
</div>

<div data-bind="with: mentions">
    <h2 data-bind="text: name"></h2>
    <ul data-bind="foreach: tweets">
        <h3 data-bind="text: name"></h3>
        <p data-bind="text: text"></p>
    </ul>
</div>

<script type="text/javascript">
var viewModel = {
    home: {
        name: "ホーム",
        tweets: ko.observableArray([
            { name: ko.observable("naoki0311"), text: ko.observable("ぎゃるげー") },
            { name: ko.observable("naoki0311"), text: ko.observable("kdwft") }
        ])
    },
    mentions: {
        name: "返信",
        tweets: ko.observableArray([
            { name: ko.observable("normalian"), text: ko.observable("割と普通") },
            { name: ko.observable("harutama"), text: ko.observable("抱き枕") }
        ])
    }
};

ko.applyBindings(viewModel);
</script>

サンプルはこちら → http://jsfiddle.net/shibayan/eegpj/

この例では home と mentions という二つの ViewModel をそれぞれの div 毎に割り当てています。WPF/Silverlight での DataContext へのバインディングと同じですね。

Containerless control flow

名前の通りコンテナが不要なフローです。ここでいうコンテナとは data-bind 属性を持った要素を指しています。

先程のコントロールフローのサンプルでは ul や span 要素に data-bind 属性で foreach や if を指定していましたが、これだと配列が空の場合や値が false の場合でも ul と span 要素は残ってしまっていました。これを回避するために 2.0.0 では HTML コメントを利用した方法が用意されています。

<!-- ko foreach: items -->
    <h2><span data-bind="text: name"></span><!-- ko if: new_flg --> - NEW<!-- /ko --></h2>
<!-- /ko -->

<script type="text/javascript">
var viewModel = {
    items: ko.observableArray([
        { name: "はうはう", new_flg: false },
        { name: "ほげほげ", new_flg: true }
    ])
};

ko.applyBindings(viewModel);
</script>

サンプルはこちら → http://jsfiddle.net/shibayan/Mc4th/

ASPX など一般的なテンプレートエンジンのような書き方ですよね。多くの場合は Containerless control flow の方が書きやすいかもしれません。

注意点としては開始タグと閉じタグが必要ということですね。この対応がちゃんと出来ていないと、もちろんエラーになります。

Access to parent binding contexts

data-bind 属性内で使える特殊な変数として $data / $parent / $parents / $root が追加されました。それぞれ現在のデータ、親のデータ、レベルごとの親、ルートという感じです。

$parents は配列なので添え字でアクセスします。ちなみに $parents[0] は $parent と同じになります。

$root は殆どの場合 ko.applyBindings で指定した ViewModel になりますが、applyBindings は DOM 要素ごとに実行することが出来るので注意です。

<!-- $parent で親のプロパティを参照する -->
<h2><span data-bind="text: $parent.category"></span> - <span data-bind="text: title"></span></h2>

Cleaner event handling

data-bind 属性の中に function() { ... } を直接書くことが出来ますが、これはロジックをビューに埋め込んでしまうことになるので、メンテナンスもやりにくいしそもそも美しくないですね。しかし、レンダリングに使われたデータを取得するためには、関数を埋め込む方法しかありませんでした。

2.0.0 からは ko.dataFor / ko.contextFor メソッドが追加され、DOM 要素からデータとコンテキストを取得することが出来るようになりました。

<ul data-bind="foreach: items">
    <li><span data-bind="text: name"></span> - <a href="#" class="remove-item">削除</a></li>
</ul>

名前: <input type="text" data-bind="value: inputName" /> <input type="button" value="追加" data-bind="click: append" />

<script type="text/javascript">
var viewModel = {
    items: ko.observableArray(),
    inputName: ko.observable(),
    append: function() {
        this.items.push({ name: this.inputName() });

        this.inputName("");
    }
};

$(".remove-item").live("click", function() {
    // 削除対象のデータを取得して
    var data = ko.dataFor(this);

    // 配列から削除!
    viewModel.items.remove(data);
});

ko.applyBindings(viewModel);
</script>

サンプルはこちら → http://jsfiddle.net/shibayan/xuKQw/

jQuery の live メソッドを使って削除リンクがクリックされた時のイベントを登録してます。クリックされた時には ko.dataFor メソッドを使って削除対象のデータを取得し、配列から削除しています。イベントハンドラを外に出すことで可読性が良くなりましたね。

1.2.1 までは更新されない jQuery Templates を使っていたのがちょっとネックでしたが、2.0.0 からは依存関係も減ったので採用しやすくなったと思います。

動的な DOM の生成は本当に手間がかかる割に需要がある部分だと思うので、knockout.js を使って楽しちゃってください。

knockout.js の基本と 1.3.0 の新機能

C# Advent Calendar 2011 で書いた knockout.js の記事が割と好評なので、調子に乗って knockout.js の解説を書いてしまおうという作戦です。

何故 knockout.js かというのは、今仕事で knockout.js を全力で使っているという非常にシンプルな理由です。今回 knockout.js を使ったケースではフォームを動的に追加・削除が必要で、さらにそのフォーム内にもさらに別のフォームを動的に追加・削除・編集が必要という、結構複雑な画面です。

最初は jQuery Templates だけを使っていたんですが、クライアントサイドでもデータを保持する必要があったので knockout.js を使ってみたところ、一気にコードがシンプルになりました。割と惚れ込んでいます。

screenshot

まずは knockout.js 自体の説明を行いたいので、とりあえず公式ページから Key concepts を引っ張ってきました。

  • Declarative Bindings
  • Automatic UI Refresh
  • Dependency Tracking
  • Templating

日本語にすると「宣言型バインディング」「UI の自動更新」「依存関係の追跡」「テンプレート」といったところでしょうか。余談になりますが、WPF や Silverlight をやったことのある人ならどこかで聞いたことのあるようなキーワードが勢揃いだと思います。

それでは一つずつ例を挙げつつ解説していきたいと思います。

Declarative Bindings(宣言型バインディング)

knockout.js では data-bind 属性を使ってバインディングの宣言を行うようになっています。例えば span の内容を name というプロパティに結び付けたい場合には以下のように宣言します。

<span data-bind="text: name"></span>

data-bind 属性の値には JSON に似た形式で書く必要があります。この例では text バインディングを使って、name プロパティを結び付けています。text バインディングは DOM で言う innerText や textContent に値をセットする機能を持ちます。

text バインディング以外にも html/css/value/checked など数多くのバインディングが用意されています。

Automatic UI Refresh

先程の例では span の内容に対してバインディングを行いましたが、内容の変更以外にも visible バインディングを使えば要素の表示・非表示を制御出来ます。以下の例では text が空じゃないときだけ div 要素が表示されます。

<div data-bind="visible: text().length > 0">
はうはう
</div>

最大の特徴ともいえるのが template バインディングで、配列から複数の UI 要素を動的に生成することが出来ます。パラメータはいくつか指定できますが、foreach パラメータで配列を指定するのがお手軽でおススメです。

<ul data-bind="template: { name: 'listTemplate', foreach: items }"></ul>

<script id="listTemplate" type="text/x-jquery-tmpl">
<!-- テンプレートタグ、データバインディングの両方が利用可能 -->
<li><span>${name}</span> - <span data-bind="text: name"></span></li>
</script>

当然ながら配列の変更を追跡しているので、要素を追加した場合には対応した UI 要素も追加表示されます。

Dependency Tracking

複数のプロパティの値を利用したバインディングを行いたい場合用に dependentObservable というメソッドが用意されています。引数として値を返すだけの関数オブジェクトを指定するだけです。

// x, y は ko.observable
viewModel.total = ko.dependentObservable(function() {
    return this.x() + this.y();
}, viewModel);

この例では x と y の値を足したものを返しているだけですが、x と y のどちらかの値が変わると、自動的に total プロパティにも変更が反映されるようになっています。

Templating

バージョン 1.2.1 では jQuery Templates をテンプレートエンジンとして使うことで、オブジェクトの配列から複数の UI 要素を一気に生成することが出来ます。先程、UI 要素生成で使用したやつですね。

現在 RC 版がリリースされている 1.3.0 ではテンプレートエンジンが内蔵されているので、jQuery Templates が不要になっています。残念ながら jQuery Templates は正式版リリース前に開発が終わってしまいましたので、1.3.0 からは内蔵テンプレートエンジンを使う機会が多くなると思います。

基本的な使い方

まずは簡単なサンプルコードで説明を行っていきたいと思います。姓、名、年齢を入力するフォームと、それを出力するための領域を今回用意しました。

<div>
	<label>姓:<input type="text" data-bind="value: lastName" /></label><br />
	<label>名:<input type="text" data-bind="value: firstName" /></label><br />
	<label>年齢:<input type="text" data-bind="value: age" /></label>
</div>
<br />
<div>
	氏名:<span data-bind="text: fullName"></span>
	<br />
	年齢:<span data-bind="text: age"></span></div>

<script type="text/javascript">
var viewModel = {
    lastName: ko.observable(""),
    firstName: ko.observable(""),
    age: ko.observable()
};

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

ko.applyBindings(viewModel);
</script>

サンプルコードは以下のリンクから動いてるものが確認できます。

http://jsfiddle.net/shibayan/kPUyN/

それではフォームに何か入力してみてください。そしてフォーカスが外れた瞬間に入力内容が出力されていることが分かると思います。ここからは少し細かく説明をしていきます。

オブジェクトを作成して viewModel という名前の変数に入れているだけですが、プロパティの値として ko.observable メソッドを呼び出した結果をセットしています。knockout.js の心臓部と言っても過言ではないのが ko.observable メソッドで、値の保持や変更追跡を行うオブジェクトを作成して返してくれます。

var viewModel = {
    lastName: ko.observable(""),
    firstName: ko.observable(""),
    age: ko.observable()
};

注意点としてはプロパティの値はオブジェクトになっているので、値の取得と設定は普通のプロパティのように行えません。そのままだと関数オブジェクトが返ってきて残念な結果になります。

// 引数無しで呼び出してあげると値が取れる
var lastName = viewModel.lastName();

// 引数をセットして呼び出すと値を設定
viewModel.lastName("ほげほげ");

そして Key concepts の説明時にも出てきた dependentObservable ですが、ここでは単純に姓と名の間にスペースを入れて結合し、フルネームを作成しているだけです。

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

そして viewModel の準備が完了したら ko.applyBindings を呼び出してバインディングを有効にします。引数には作成した viewModel を指定するのを忘れずに。

ko.applyBindings(viewModel);

これで後は knockout が変更追跡や UI の自動生成を行ってくれます。非常に簡単ですね!

knockout 1.3.0

まずは開発者である Steven Sanderson さんのブログを確認しておきましょう。

Knockout 1.3.0 Beta Available - Steve Sanderson’s blog - As seen on YouTube™

と思ったんですが、github を確認したところ 2.0.0 rc というものが公開されていました…。恐らくは 1.3.0 → 2.0.0 に変わっただけだと思うのですが、変更点がまだよくわかっていませんので、後日別エントリでまとめてみたいと思います。

ちなみにバージョン 1.3.0 ではバインディングのコンテキストを明示的に指定出来たり、テンプレートエンジンが内蔵されていたり、バインディングプロバイダが拡張できるようになってたりします。個人的には以下のようなコードでテンプレートを実現できる 1.3.0 の記法は好みです。

<!-- 子要素の li がそのままテンプレートとして扱われる -->
<ul data-bind="foreach: items">
    <li><span data-bind="text: name"></span> - <span data-bind="text: age"></span></li>
</ul>

今回 knockout.js の説明を長々と書きましたが、実は MIX11 の時に knockout.js 開発者の Steven Sanderson さんがデモをやっていたみたいです。

screenshot
http://channel9.msdn.com/Events/MIX/MIX11/FRM08

30 分近くの動画なので時間がある時にでもどうぞ。knockout.js は非常に使い勝手がいいライブラリなので、これからも使っていきたいですね!

ASP.NET MVC 4 で knockout.js を活用する

これは C# Advent Calendar 2011 用の記事です。
皆さんこんばんは、もうちょっとで今年一年終わりですね。まあ、そんなことはどうでもいいとして Ever17 の箱版プレイしましょうね。

さて、今回は C# Advent Calendar と言いながら JavaScript かよ!という感じですが ASP.NET MVC 4 なので問題ないんですよ!!ASP.NET MVC 4 を入れてる人が少ないので知ってる人が少なそうですが、標準で knockout.js が NuGet パッケージとして入るようになっています。

screenshot

http://knockoutjs.com/

knockout.js を分かりやすく説明すると WPF/Silverlight のデータバインディング・ItemsControl・データテンプレートを HTML で実現するためのライブラリです。

デフォルトで入るようになっていますが、参照はされていないので _Layout.cshtml の head タグに以下の 1 行を追加します。

<script src="@Url.Content("~/Scripts/knockout-1.3.0beta.js")" type="text/javascript"></script>

とりあえずしょぼいですが、足し算するようなフォームを knockout.js で作ってみます。まずはスクリプトから。

<script type="text/javascript">
    var viewModel = {
        left: ko.observable(0),
        right: ko.observable(0)
    };

    viewModel.answer = ko.dependentObservable(function () {
        return parseInt(this.left()) + parseInt(this.right());
    }, viewModel);

    ko.applyBindings(viewModel);
</script>

ViewModel に関してはあまり言及しませんが、left と right というプロパティを用意します。ko.observable は WPF/Silverlight の DependencyProperty と同じものだと考えてください。これで定義したプロパティは変更通知が動作するようになります。

そして answer では ko.dependentObservable を使って定義しています。これは WPF では IValueConverter と IMultiValueConverter に相当していて、値に対して何らかの変換を行って返すことが出来ます。ちなみに、内部で使ってるプロパティの値が変わった時にもちゃんと追従してくれます。

そして HTML を用意します。注意点としては先程のスクリプトよりも前に書いておく必要があります。

<input type="text" data-bind="value: left" /> + <input type="text" data-bind="value: right" /> = <span data-bind="text: answer"></span>

HTML5 の Custom Data Attribute を使ってバインディングを定義します。data-bind 属性は {Binding} 式と同じものだと思ってください。バインディングのターゲットとして value や text など以外に任意の属性を指定することもできます。詳しくは公式サイトのドキュメントを見てくださいね。

実行すると以下のような感じです。jQuery などで変更されたか調べて、ごりごり書き換えたり指定はいませんが、リアルタイムで値が更新されます。

さて、これで終わってしまうと C# 関係ねえと言われそうというか、既に言われてそうなので ASP.NET MVC と連携させます。JavaScript なので JSON で値を返すと簡単に扱えるようになります。そして ASP.NET MVC には JsonResult があるので、簡単に JSON で返すことが出来ますね。

public ActionResult IndexAjax()
{
    var list = new[]
    {
        new { name = "北兄者", score = 100 },
        new { name = "兄者", score = 150 },
        new { name = "抱き枕兄者", score = 100 }
    };

    return Json(list, JsonRequestBehavior.AllowGet);
}

これで Json を出力することが出来ます。AllowGet を忘れないようにしてください。

そして JavaScript と HTML を書いていきます。knockout 1.3.0 からはテンプレートエンジンが内蔵されているので、jQuery Template を必要としなくなりました。

<ul data-bind="foreach: items">
    <li>名前: <span data-bind="text: name"></span>, スコア: <span data-bind="text: score"></span></li>
</ul>

<script type="text/javascript">
    $(function () {
        $.getJSON("@Url.Action("IndexAjax")", function (data) {
            var viewModel = {
                items: ko.observableArray(data)
            };

            ko.applyBindings(viewModel);
        });
    });
</script>

data-bind 属性で foreach を指定すると中身をテンプレートとして扱ってくれます。この辺りは DataTemplate と同じような感じで使い勝手が良いですね。もちろん変更追跡が有効になっているので、ko.observableArray にアイテムを追加すると動的に追加されます。

ASP.NET MVC だから HTML5 や JavaScript のライブラリが使えないとか、使うのがめんどくさいとかいうことは全くないんですよ!!