1

我有 knockout.js 子视图模型,observableArray它们依赖于observable父视图模型。父级observable绑定到select下拉列表,子级需要知道该值何时更改。

我已经传递了整个父视图模型,只是observable, 并且还在父级内部订阅了observable,以便“手动”更新子级。所有这些都用于绑定目的。

问题是,如果我随后清除孩子observableArray为新孩子腾出空间,或者简单地用新孩子替换孩子,那么旧孩子会留在内存中,因为它们仍然被敲除.js 设置的反向依赖项引用。

我对不同的设计模式或告诉淘汰赛停止依赖依赖项的方法持开放态度。在另一个示例中,我尝试了 cleanNode() ,但这似乎并没有清除这些反向依赖项。

代码:

function FamilyViewModel(name) {
    this.name = name;
    this.children = ko.observableArray();
}

function ChildViewModel(firstName, lastName, selectedFamilyObservable) {
    this.firstName = ko.observable(firstName);
    this.lastName = ko.observable(lastName);
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
    this.isSelected = ko.computed(function() {
        return selectedFamilyObservable().name == this.lastName();
    }, this);
}

function PageViewModel() {
    this.families = ko.observableArray([
            new FamilyViewModel("Smith"),
            new FamilyViewModel("Jones"),
            new FamilyViewModel("Brown")
            ]);
    this.selectedFamily = ko.observable();
    this.addChild = _.bind(function() {
        this.selectedFamily().children.push(new ChildViewModel("Frank", this.selectedFamily().name, this.selectedFamily));
    }, this);
    this.resetChildren = _.bind(function() {
        this.selectedFamily().children([]);
    }, this);
}

$(function() {
    ko.applyBindings(new PageViewModel());
});

小提琴:

http://jsfiddle.net/cygnl7/xhzkC/1/

4

2 回答 2

2

Check my comments, as I think they will help you a bit.

I made some changes to your fiddle. I didn't quite understand why you included underscore as it was not needed to bind the events. Also, your childViewModel and familyViewModel's were both actually just models. The isSelected property on the family was just an extra binding that meant each time you changed the selectedFamily you had to go out and do additional updates. Last, if you want to reset the children for the selectedFamily only, just remove the ko.utils.arrayForEach function and just do selectedFamily().children([]); I am not sure why you would want to only reset the selected families children but it is pretty easy to do.

Needs code -

 var addChild = function () {
      self.selectedFamily().push(new childModel(self.selectedFamily().name());
 };

http://jsfiddle.net/xhzkC/2/

于 2013-09-14T01:46:45.553 回答
1

前面提到的小提琴解决方案。

问题的症结在于,您不仅要跟踪当前选定的族 ( .selectedFamily),而且还希望每个族都知道它们是否被选中。selectedFamily我认为最简单的解决方案是在它改变之前和改变之后订阅和监控它的价值来设置一个家庭的isSelected价值。我喜欢这种方法,因为不需要遍历家庭数组来执行此操作。

self.selectedFamily = ko.observable();
self.selectedFamily.subscribe(function (oldValue) {
    if (oldValue) {
        oldValue.isSelected(false);
    }
}, null, 'beforeChange');
self.selectedFamily.subscribe(function (newValue) {
    if (newValue) {
       newValue.isSelected(true);
    }
});

订阅默认功能在值已更改传入新值。在这里,您可以访问新选择的族,并且可以将其isSelected属性设置为 true。但是订阅有两个默认调用的内置主题。第一个是我们刚刚讨论过的“改变”主题,另一个是“改变之前”。这会在值更改之前调用,并让您有机会在值即将更改之前执行任何业务逻辑,例如将isSelected标志设置为 false。

----------

请注意,下一部分只是我经历的一些注意事项和额外信息。以上是一个足够的解决方案,但对于那些想了解更多的人,请继续阅读......

----------

另一种方法是selectedFamily使用私有可观察支持计算读/写,如下所示:

var _selectedFamily = ko.observable();
self.selectedFamily = ko.computed({
    read: _selectedFamily,
    write: function (newValue) {
        var oldValue = _selectedFamily();
        if (oldValue) {
            oldValue.isSelected(false);
        }
        if (newValue) {
           newValue.isSelected(true);
        }
        _selectedFamily(newValue);
    }
});

我如何进行此设置并不是万无一失的,但应该让您对这里的概念有所了解。这不是一个坏方法,如果我需要根据当前值和下一个值来决定设置计算结果的结果,我会采用这种方法。但在这种情况下,我不喜欢isSelected在这里设置标志,因为它与结果值无关。假设需要创建一个计算来容纳确定设置值的结果的逻辑,我仍然会选择订阅解决方案,如前所述,但在私人支持的_selectedFamilyobservable 上,如果有机会需要在不经过计算的情况下设置私有返回的 observable 的值。


我在代码中提到的另一部分是按钮上的点击绑定。我把它们包在一个with装订里。默认情况下,绑定到函数的绑定将当前上下文的对象传递给它们。现在不必这样做:

self.addChild = function () {
    var selectedFamily = self.selectedFamily();
    // ...code...
};

你可以做:

self.addChild = function (selectedFamily) {
};

我还把<span data-bind="visible: isSelected">| Selected!</span>绑定从儿童上下文移到了家庭上下文中,因为我认为这是我的意图。你有 '| 看起来很奇怪 isSelected' 每个孩子旁边的文本而不是姓氏,但是,如果您希望每个孩子都使用它,您可以这样做:

<li data-bind="text: fullName"><span data-bind="visible: $parent.isSelected">| Selected!</span></li>

关于 KOforeach绑定的最后一个特性是与具有 data/as 属性的对象绑定。它可以使您的标记更具人类可读性,并且确实有助于访问父链。假设我们确实想使用家庭的isSelected旗帜来显示孩子们的名字。上面我们必须引用 using $parent。在某些情况下,您有多个嵌套foreach绑定,您可能会发现自己编写的内容<span data-bind="text: $parents[2].name"></span>很少提供有关您尝试引用的上下文的信息。使用此方法,您可以改为编写:

<div data-bind="foreach: { data: $root.families, as: 'family' }">
    <ul data-bind="foreach: { data: $family.children, as: 'child }>
        <li>
            <span data-bind="text: child.firstName">
            <span data-bind="text: family.name">
        </li>
    </ul>
</div>

这样,您甚至不需要计算来写出名字和姓氏。当我认为我将拥有一个复杂的嵌套上下文绑定结构和/或当我想让一个简单的嵌套结构看起来更易于阅读时,我会使用这种方法。

http://jsfiddle.net/34ZjB/1/

于 2013-09-14T19:30:12.453 回答