【问题标题】:Reevaluating a Knockout computed which depends just on an observable array重新评估仅依赖于可观察数组的 Knockout 计算
【发布时间】:2014-01-25 07:54:45
【问题描述】:

我的 Appmodel 包含一个可观察的 cmets 数组

self.comments=ko.observableArray([]);  // Default Value is an empty array
/*
   Here comes some code to initially fill the observable array 
   with items from an JSON Response
*/

此外,我有两个计算值,它们应该代表第一条评论和最后一条评论

self.firstComment = ko.computed(function () {
    var sorted = self.comments.sort(function (left, right) {
        return left.Id() - right.Id();
    });
    return sorted[0];
});

self.lastComment = ko.computed(function () {
    var sorted = self.comments.sort(function (left, right) {
        return left.Id() - right.Id();
    });
    return sorted[sorted.length - 1];
});

这在初始化应用程序(从服务器加载 JSON,建立 App 模型......)时非常有效,但是当我向数组添加注释时,计算无法识别数组项的数量已更改(据我了解,可观察数组只是观察数组属性本身的可观察对象)。所以当我这样做时:

self.comments.push(aNewCommentObject);

self.lastComment 仍然绑定到数组项,即应用最初加载时的状态。

我发现this blog post 如何通过引入一个虚拟的 observable 来强制计算,但我不喜欢这种方法。那么 observableArray 用于什么目的以及如何使用?

其他挑战:我希望在任何情况下都对 observableArray 项进行排序(因为它是一个应该按时间顺序排序的评论提要)。我尝试使用计算出的commentsSorted 来执行此操作,但也存在当 observableArray 有新项目时它不会更新的问题,所以这里也有同样的问题。这就是为什么我每次都在 firstComment 和 lastComment 中排序的原因。

【问题讨论】:

    标签: javascript arrays knockout.js


    【解决方案1】:

    尝试解开 cmets 以触发 Knockout 的依赖跟踪:

    self.firstComment = ko.computed(function () {
        var sorted = self.comments().sort(function (left, right) {
            // -------------------^^ !
            return left.Id() - right.Id();
        });
        return sorted[0];
    });
    

    或(相同的东西):

    self.firstComment = ko.computed(function () {
        var sorted = ko.unwrap(self.comments).sort(function (left, right) {
            return left.Id() - right.Id();
        });
        return sorted[0];
    });
    

    当然你可以把它抽象成一个单独的计算。

    self.commentsSorted = ko.computed(function () {
        return self.comments().sort(function (left, right) {
            return left.Id() - right.Id();
        });
    });
    
    self.firstComment = ko.computed(function () {
        return ko.unwrap(self.commentsSorted)[0];
    });
    

    由于计算缓存了它们的返回值(就像所有其他 observable 一样),您无需担心多次调用self.commentsSorted。只有当底层的 observable 数组有机会时才会重新计算。

    【讨论】:

    • 哦,天哪,就是这样。作为淘汰赛的第一个计时器,我在一个项目上工作了大约 2 个月,如果我目前面临的对象是可观察的并且需要解开包装,我真的在 95% 的时间里都在挣扎。代码因此变得更加复杂,但我希望 Knockout 和我很快能交到朋友 ;) 非常感谢,我认为下一个 Knockout 问题指日可待
    • 只需在所有内容上使用ko.unwrap。它将处理 observables 和普通值,您无需担心哪个是哪个(并且您的代码也可以通过这种方式变得更加灵活)
    • @Jürgen 例如,function (left, right) { return ko.unwrap(left.Id) - ko.unwrap(right.Id); } 更加灵活和健壮,因为它可以处理目标对象的映射和未映射版本。
    • sort 是一种变异方法。它修改底层数组。当在问题self.comments.sort 中调用时,它会修改数组并通知任何订阅者和绑定。如果在答案self.comments().sort 中调用,它仍然会修改数组,但不会通知订阅者或绑定。这是一个简单的解决方案:if (self.comments()) { self.comments.sort(...); }。现在你得到了两者,对数组的依赖和更改通知。
    • @Michael:我认为这里的主要问题是,self.comments.sort 返回一个 observable,而不是我的数组。所以返回的对象不能仅通过索引访问。通过展开它,我得到一个排序数组,并且可以通过相应的索引获取第一个或最后一个元素。在此示例中,我不需要收到有关排序的通知,我想在推送新元素时收到通知(实际上已经在工作,我只是无法以正确的方式使用生成的对象,我的错;) )
    【解决方案2】:

    你如何“在任何情况下保持 observableArray 项的排序”?我的建议是不要尝试对原始数组进行排序(我将在下面更详细地描述陷阱),而是使用一个返回一个新的排序数组的计算 observable:

    self.commentsSorted = ko.computed(function () {
        return self.comments.slice(0).sort(function (left, right) {
            return left.Id() - right.Id();
        });
    });
    

    了解 JavaScript Array sort() 方法的重要一点是它更改了原始数组。 Knockout 的observableArray 包装了许多数组函数,因此可以直接在observableArray 对象上使用它们。这就是您可以使用myObservableArray.sort() 而不必使用myObservableArray().sort() 的原因。但两者并不等价,因为 Knockout 以不同的方式包装了数组 mutator 方法,大致如下:

    sort: function (sortFunction) {
        this.valueWillMutate();
        var result = this.peek().sort(sortFunction);
        this.valueHasMutated();
        return result;
    }
    

    那么自动更改原来的observableArray有什么问题呢?

    1. 很难理解发生了什么。很容易要么不依赖数组,因此在初始化之后永远不会更新,要么不通知更改,从而破坏其他元素获取数组的正确视图。

    2. 即使您正确设置了它,您现在也已经创建了一个循环依赖项,因为您拥有在数组更改时更新的内容,然后再次更新数组。尽管 Knockout 当前禁用了此功能以同步更新计算的 observable(但将来可能会改变),但如果使用手动订阅或使用 throttle 选项计算的,它将导致无限递归。

    【讨论】:

    • 这是关于 observableArrays 的重要知识,也是一个很好的答案。我将使用 slice() 将数组复制到计算中,但我对此有更多疑问。如上所述,我在初始化期间将 cmets 批量加载到我的数组中(大约 500 个问题,每个问题大约有 5 到 10 个 cmets)。我使用数组的 push() 函数来加载它们。不是每个 push() 都会触发 commentsSorted 的重新计算吗?不是每个 slice() 都创建一个全新的数组吗?这不是非常慢而且内存效率低吗?我想我应该在这里暂时停用依赖跟踪?
    • 将多个项目加载到可观察数组中时,可以使用myObsArray.push.apply(myObsArray, items)
    【解决方案3】:

    替代解决方案,使用subscribe

    self.comments = ko.observableArray([]);
    
    self.comments.subscribe(function(comments) {
        var sorted = comments.sort(function (left, right) {
            return left.Id() - right.Id();
        });
    
        self.firstComment(sorted[0]);
        self.lastComment(sorted[sorted.length - 1]);
    
    });
    

    【讨论】:

    • 据我了解,如果没有其他可能,就应该使用订阅,这意味着如果你不能使用计算或者你的 UI 中有特殊的东西,你就不能用绑定解决。我已经有了一个循环依赖(订阅函数改变了另一个可观察对象,它本身改变了我再次订阅的对象,依此类推)。可怕的调试场景......
    猜你喜欢
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 2014-12-28
    • 1970-01-01
    • 1970-01-01
    • 2014-01-28
    • 1970-01-01
    相关资源
    最近更新 更多