Observable (ViewModelをつくる)
Knockout は次の3つの思想に基づいています。
- Observable と依存関係トラッキング
- 宣言型バインディング
- UIテンプレート
ここでは上記3つのうち最初の1つを紹介しますが、その前に、MVVM パターンと View Model のコンセプトについて考えてみましょう。
MVVM と View Model
Model-View-ViewModel (MVVM) はユーザインターフェイスを構築するための設計パターンであり、概念モデルです。 MVVM では、プログラムを次の3つに分割して設計することで、機能的なUIのコードをシンプルに保ちます。
-
Model: いかなるUIにも依存しない、ビジネスドメインのデータと操作を表すオブジェクトです。
Knockout を使う場合、サーバに保管されたデータを取得・変更するために、サーバサイドコードをAjaxで呼び出すことになるでしょう。 -
ViewModel: UIで必要とされるデータと操作を表現する、純粋なオブジェクトです。 例えば、アイテムを追加・削除できるリストを実装するとき、ViewModel では データ「アイテムのリスト」 操作「追加」「削除」 を公開します。
ViewModel自体 は UI ではないことに注意してください。ボタンや表示スタイルに関するいかなる情報も含みません。 さらに ViewModel は永続データモデルではありません。ユーザが画面を操作する上で、保存されていない(あくまでメモリ上の)データを保持します。 Knockout を使えば、ViewModel はいかなるHTMLに関する情報を含まない、純粋な JavaScript オブジェクトとなります。 このように ViewModel を抽象的に保つことで、簡潔さを損なうこと無く改良を加えていくことができます。
-
View: ViewModel の状態に応じてインタラクティブに変化する UI です。 ViewModel が提供する情報を表示し、ViewModel にコマンドを送ります。(ユーザがボタンをクリックした時など) そして ViewModel に変化があれば更新します。
Knockout を使う場合、バインディングで ViewModel とリンクしたシンプルなHTMLドキュメントが View となります。 あるいは、テンプレートを使って ViewModel のデータから HTML を生成することができます。
Knockout で ViewModel を作るには、次のように JavaScript オブジェクトを定義するだけです。
var myViewModel = { personName: 'ボブ', personAge: 123 };
その後、宣言型バインディングを使用して、この ViewModel の View を作成します。次の例は「personName」を表示する場合です。
お名前: <span data-bind="text: personName"></span>
Knockout を作動させよう
data-bind
属性は HTML 標準ではありませんが、問題ありません。(HTML5に厳密に準拠しており、HTML4においては解釈されない属性は無視されます。)
しかしブラウザは data-bind
が何を意味するか知らないため、バインディングを有効にするために Knockout を作動させる必要があります。
Knockout を作動させるには、<script>
タグ内に次のように記述します。
ko.applyBindings(myViewModel);
上記のスクリプトは、HTMLの最下部に配置してください。もしくは、DOMのreadyイベントハンドラでラップする (jQueryの $(function(){ ~ })などのように )ことで、<head>タグやその他どこにでも配置することができます。
これで完了です!記述されたHTMLに従って、Viewが次のように展開されます。
お名前: <span>Bob</span>
ko.applyBindings は次の引数(最大2つ)を受け取ります。
-
1つ目の引数は、View に対してバインドすべき ViewModel です。
-
2つ目の引数はオプションです。ViewModel をバインドする対象のDOM要素を指定することができます。
例:ko.applyBindings(myViewModel, document.getElementById('someElementId'))
これにより、ID「someElementId」が付与された要素と、その配下の要素に対してのみバインドを適用することができます。 1つのページに対して、部分ごとに異なる ViewModel をバインドさせるといった使い方ができます。
実にシンプルですね。
Observable
基本的な ViewModel のつくりかたと、どのようにして ViewModel のプロパティを画面に表示するかをご理解頂けたかと思います。 しかし、Knockout の最大のメリットのひとつは「 ViewModel が変更されると自動的にUIが更新される」 ということです。 Knockout はいかにして ViewModel の変更を知ることができるのでしょうか。 その答えは、ViewModel のプロパティを Observable (=オブザーバブル=監視可能) として定義することです。 Observable は特殊な JavaScript オブジェクトで、プロパティのサブスクライバー (=購読者) に対して変更を知らせ、 かつ自動的に依存関係を検知できる仕組みがあります。
先ほどの ViewModel を次のように書き換えてみましょう。
var myViewModel = { personName: ko.observable('ボブ'), personAge: ko.observable(123) };
View を変更する必要はありません。data-bind
は先程記述した内容のままで動作します。
変わったところは、変更を検知できるようになったことです。プロパティが変更されれば、View が自動的に更新されます。
Observable を読み書きする
JavaScript には getter/setter 構文がありますが、残念ながら全てのブラウザで実装されているわけではありません(IEゲフンゲフン...)。
そこで互換性を確保するため、ko.observable
オブジェクトの実態は function です。
-
Observable の現在の値を取得 するには、引数なしで observable をコールします。 今回の例では、
myViewModel.personName()
は'ボブ'
を、myViewModel.personAge()
は123
を返却します。 -
Observable に新しい値をセット するには、新しい値を引数に observable をコールします。 例えば、
myViewModel.personName('メアリー')
とすると名前が'メアリー'
に変わります。 -
複数の Observable プロパティに新しい値をセット する場合、メソッドチェーンが便利です。 例えば、
myViewModel.personName('メアリー').personAge(50)
とすると名前は'メアリー'
に、年齢は50
に変わります。
Observable の核心は監視できることにあります。言い換えると、変更通知を受け取るコードが別にあるということです。
実際それは Knockout の組み込みバインディングの多くが内部で行なっていることです。
data-bind="text: personName"
と書くと、text
バインディングは
personName
の変更通知を受け取るように登録されます。
myViewModel.personName('メアリー')
を呼び出して名前を 'メアリー'
に変更すると、
text
バインディングは関連付けられた DOM 要素のテキストを自動的に更新します。
以上が、 ViewModel の変更が View に伝播する仕組みです。
Observable の変更通知を明示的に購読する
通常の用途では、手動で購読することはありません。入門者の方はこのセクションを読み飛ばして下さい。
Observable の変更通知を購読する方法を示します。 Observable の subscribe 関数をコールします。
myViewModel.personName.subscribe(function(newValue) { alert("この人の新しい名前は " + newValue + "だそうです。"); });
subscribe
関数は Knockout ライブラリの中で非常に多く使用されています。
また、購読を止める必要がある場合は、次のように dispose
関数を呼び出します。
var subscription = myViewModel.personName.subscribe(function(newValue) { // なにかする }); // ...その後 subscription.dispose(); // もう通知は不要です
ほとんどの場面において、このように購読する必要はありません。 なぜなら、組み込みバインディング及びテンプレートシステムが購読の管理をしてくれるからです。