web-dev-qa-db-ja.com

.computed()オブザーバブル内からノックアウトの$ parent / $ root疑似変数を使用するにはどうすればよいですか?

knockout.js バインディング式内で、 $data$parent、および$root疑似変数 を使用できます。 JavaScriptで宣言された ko.computed observable を使用しているときに、これらの擬似変数に相当するものを取得するにはどうすればよいですか?

子のコレクションを持つ親ビューモデルがあり、親ビューモデルにはselectedChildオブザーバブルがあります。そのため、データバインディング式を使用して、現在選択されている子にCSSクラスを追加できます。

<ul data-bind="foreach: children">
    <li data-bind="text: name,
                   css: {selected: $data === $root.selectedChild()},
                   click: $root.selectChild"></li>
</ul>
<script>
vm = {
    selectedChild: ko.observable(),
    children: [{name: 'Bob'}, {name: 'Ned'}],
    selectChild: function(child) { vm.selectedChild(child); }
};
ko.applyBindings(vm);
</script>

しかし、私のビューモデルはより複雑になり、「私は選択しますか?」単一のCSSクラスを単一の要素に追加するだけではありません。子ビューモデルでisSelected計算プロパティを作成したいので、それに依存する他の計算プロパティを追加できます。

ノックアウトがこれらの変数を定義し、computedエバリュエーターを呼び出すときにスコープ内にある可能性があるという偶然に、$dataおよび$rootを参照するJavaScriptを記述しようとしました。関数:

{
  name: 'Bob',
  isSelected: ko.computed(function(){ return $data === $root.selectedChild(); })
}

しかし、そのような運はありません。私の評価者functionの中では、$data$rootの両方がundefinedです。

また、エバリュエーター内で ko.contextFor を使用しようとしました。これは、$dataおよび$rootへのアクセスを許可するためです。残念ながら、評価関数内では、contextForも常にundefinedを返します。 (とにかくこの戦略に大きな期待を抱きませんでした-このように後ろに行かなければならないとしたら、ノックアウトが依存関係をどれだけうまく追跡できるかは明確ではありません。)

親ビューモデルを参照する子ビューモデルごとに、常に手動でプロパティを設定できました。しかし、ノックアウトにはこれを行う能力があることを知っているので、自分で書く前に、少なくともそのメカニズムを使用できるかどうかを調べたいと思います。

上記のバインディング式を計算されたオブザーバブルに変換することが可能であるように思われます-結局のところ それはすでにノックアウトが行っていることです

もう1つの巧妙なトリックは、宣言型バインディングが計算されたオブザーバブルとして単純に実装されることです。

しかし、自分で計算されたオブザーバブルを記述しているときに、$dataおよび$root疑似変数をどのように扱うのでしょうか。

59
Joe White

疑似変数は、データバインディングのコンテキストでのみ使用できます。ビューモデル自体は、理想的には、それを表示しているビューについて認識していないか、依存しているべきではありません。

そのため、計算されたオブザーバブルをビューモデルに追加する場合、バインドされる方法($ rootになるものなど)がわかりません。ビューモデルまたはビューモデルの一部は、異なるレベルでページの複数の領域に個別にバインドすることもできるため、擬似変数は開始する要素によって異なります。

それは何を達成しようとしているかに依存しますが、このアイテムが親ビューモデルで選択されたアイテムと同じかどうかを示すisSelected計算されたオブザーバブルを子供に持たせたい場合は、親を子が利用できるようにする方法を見つけます。

1つのオプションは、親を子のコンストラクター関数に渡すことです。ポインタを子のプロパティとして親に追加する必要さえなく、計算されたオブザーバブルで直接使用できます。

何かのようなもの:

var Item = function(name, parent) {
   this.name = ko.observable(name);  
   this.isSelected = ko.computed(function() {
       return this === parent.selectedItem();        
   }, this);
};

var ViewModel = function() {
   this.selectedItem = ko.observable();
   this.items = ko.observableArray([
       new Item("one", this),
       new Item("two", this),
       new Item("three", this)
       ]);
};

ここのサンプル: http://jsfiddle.net/rniemeyer/BuH7N/

選択したステータスだけが必要な場合は、それを調整して、次のようなselectedItem observableへの参照を子コンストラクターに渡すことができます。 http://jsfiddle.net/rniemeyer/R5MtC/

親ビューモデルがグローバル変数に格納されている場合、次のように子に渡さずに直接使用することを検討できます。 http://jsfiddle.net/rniemeyer/3drUL/ 。ただし、参照を子供に渡すことを好みます。

77
RP Niemeyer

私の経験では、Itemsがアプリケーションの存続期間中に存在する場合、@ RP Niemeyerの答えのアプローチは問題ありません。しかし、そうでない場合、Itemの計算されたobservableはViewModelから逆の依存関係を設定するため、メモリリークにつながる可能性があります。繰り返しになりますが、Itemオブジェクトをまったく削除しなくてもかまいません。ただし、Itemsを削除しようとしても、ノックアウトにはその逆の依存関係参照が残るため、ガベージコレクションは行われません。

あなたはcould、おそらくItemのcleanup()メソッドで計算されたものを破棄することを確認しますが、アイテムがなくなると呼び出されますが、 Itemsを削除するときは、必ずそれを行う必要があります。

代わりに、Itemをもう少しスマートにし、ViewModelが選択されたときに通知するようにしないのはなぜですか? ItemisSelected()を通常の古いオブザーバブルにしてから、ViewModelselectedItemをサブスクライブし、そのサブスクリプション内で更新するだけです。

または、@ RP Niemeyerの pub/sub solution を使用します。 (公平を期すために、この解決策はここでの彼の答えの後に生まれました。)ただし、逆の依存関係も作成するため、クリーンアップする必要があります。しかし、少なくとも結合は少なくなります。

詳細については、この同じトピックの 私の最近の質問 への回答を参照してください。

1
devguydavid