Knockout.jsを使っていてハマった事
はじめに
Knockout.js 3.3.0 を使い始めて数週間ですが、
とりあえずハマった事をメモします。
Mapping
JSON から Knockout の Observable を作ってくれる便利なプラグインですが…
ko.toJSON するといらないものが付く
ko_mappings みたいなのが付いてしまう。
→ ko.mapping.toJSON を使う必要がある。
Editables
編集のロールバックや、変更されたか否かを自動で判定してくれる便利なもの。
ko.editable(hoge) すると hasChanges が追加されてしまう
ko.mapping.toJSON したときに余計なプロパティが
付いてしまう。(で、例えば Java とか型の固い言語に JSON 化して引き渡すと hasChanges を
解釈できずにコケる)
→ どうにもならないので、ko.mapping 側の ignore に追加する。hasChanges という名前をフィールドに使えなくなるが仕方が無い(editable 使う時点で使えないし)
ko.mapping.defaultOptions().ignore = ["hasChanges"];
observableArray に subscribe したは良いけど、変更が通知されない
→FAQ らしい。observableArray は中身について一切関知しない。
なので、追加/削除の時しか通知は来ない。
see: http://kojs.sukobuto.com/docs/observableArrays
・・・でもこれ、直感的な動作とは言いがたいような気がする。
じゃあ中身の変更を知りたい時は?
ObservableArray の中身のプロパティを、ko.observable で定義して(ko.mappings 使えば自動)
Array 内の全てのオブジェクトの、必要なプロパティに Subscribe すれば良い。
Subscribe するときは、次の項目の通り、 this に束縛する値に都合のよいものを指定する。
Subscribe したは良いけど、変更されたオブジェクトがハンドラに渡ってこない!
Subscribe するときの第二引数で、ハンドラの this に束縛する値をセットする必要がある。
Subscribe をやめたい
Subscribe した時に返ってくるオブジェクト.dispose() すれば OK。
ko.utils.arrayForEach(array.items(), function (item) {
var subscribe = item.value.subscribe(function (newValue) {
// 変更されたときのハンドラ
}, item); // 第二引数にちゃんと対象オブジェクトを指定しておく
subscribe.dispose(); // subscribeやめる
});
observableDictionary が Subscribe できない
実装されてないのでどうにもならない模様。私は諦めた。
ただし、中身は普通に Observable なはずなので、subscribe できるはず。
$root は出来るだけ使わない
ViewModel 一個で画面を作っている場合、$root = $parent となるのが大半でしょう。
この時、できるだけ $parent で指定しておいた方が良いです。
後々、画面が複雑化してきて、複数 ViewModel に分割した場合(以下例参照)
// 例えばこんな感じです
var viewModel = new MainViewModel();
var subModel = new SubViewModel();
ko.applyBindings({
main: viewModel
, sub: subModel
});
// HTML内
<div id="main" data-bind="with: main">
略
</div>
<div id="sub" data-bind="with:sub">
略略
</div>
元々$root で取得していたものは $root.main で参照するようになります。
できるだけ使う事をおすすめします。というより、$root を使わないといけないことは
滅多にないはずです。
→2015/08/30 追記
そもそも、大きく囲んだ要素に data-bind="with: main" と書いてあるのであれば
main の viewModel の中の要素を指定する際、プレフィックス的なものは不要。
とりあえず data-bind="with:hoge" を書いておいた方が収まりがいいと感じた。
バインディングコンテキストは理解しておいた方がよさそう。
-> 2015/09/30 追記
viewModel が一つしかない場合でも、ko.applyBindings(viewModel); は使わずに
上のコードのように連想配列を渡しておけば、後からVM分割したくなっても安心。
可能であれば if より visible を使う
if は、条件が false な時に、その中の要素を本当に消してしまいます。
条件が成立すると、中の要素を追加します。何が問題かというとバインドした
イベントが消えるということです。
visible であれば隠れるだけなのでこういった問題は発生しません。
Discussion