1

这是一个相当复杂的问题。我试图将代码尽可能地改进为仅演示问题所需的代码。这是一个很长的帖子 - 考虑一下自己被警告了!

这个问题的工作演示可以在这个jsFiddle中看到。


我要做的是根据数组中项目的共同属性将数组聚合成组。实际上将数组聚合成组是相当简单的:

function ViewModel() {
    var self = this;

    this.groupProperty = ko.observable('groupId');

    this.data = ko.observableArray();
    this.view = ko.computed(function () {
        var i, element, value, grouped = {};

        for (i = 0; i < self.data().length; i++) {
            element = self.data()[i];
        
            if (!element.hasOwnProperty(self.groupProperty()))
                continue; //Exclude objects that don't have the grouping property...
        
            value = ko.isObservable(element[self.groupProperty()]) ? element[self.groupProperty()]() : element[self.groupProperty];
        
            if (!grouped.hasOwnProperty(value))
                grouped[value] = new Group(value);
        
            grouped[value].items().push(element);
        }
    
        return transformObjectIntoArray(grouped);
    });
}

transformObjectIntoArray可以在 jsFiddle 示例中看到。它只是通过基本摆脱属性将一个对象变成一个数组。

这需要一组数据,例如:

[
    Item { label: 'Item 1', groupId: 'A' },
    Item { label: 'Item 2', groupId: 'A' },
    Item { label: 'Item 1', groupId: 'B' },
    Item { label: 'Item 2', groupId: 'B' }
]

将其转换为以下对象:

{
    'A': [
        Item { label: 'Item 1', groupId: 'A' },
        Item { label: 'Item 2', groupId: 'A' }
    ],
    'B': [
        Item { label: 'Item 1', groupId: 'B' },
        Item { label: 'Item 2', groupId: 'B' }
    ]
}

然后将其转换为以下数组:

[
    [
        Item { label: 'Item 1', groupId: 'A' },
        Item { label: 'Item 2', groupId: 'A' }
    ],
    [
        Item { label: 'Item 1', groupId: 'B' },
        Item { label: 'Item 2', groupId: 'B' }
    ]
]

到目前为止,一切都按预期工作,如 jsFiddle 所示。

当我向数据数组添加一个新元素时,问题就开始了。vm是我ViewModel在以下代码中的实例

vm.data.push(new Item('Item X', 'A')); //Add a new item called 'Item X' to the 'A' group

正如您可能已经猜到的那样,这会导致计算的 observableview执行,从而重新聚合底层数据数组。所有新Group对象都被创建,这意味着与它们相关的任何状态信息都将丢失。

这对我来说是个问题,因为我存储了它们是否应该在Group对象上展开或折叠的状态:

function Group(label) {
    this.expanded = ko.observable(false); //Expanded state is stored here
    this.items = ko.observableArray();
    this.label = ko.observable(label);
}

因此,由于它们被重新创建,该状态信息就会丢失。因此,所有组都恢复到其默认状态(折叠)。


我知道我面临的问题。但是,我正在努力想出一个并不笨拙的解决方案。我想到的可能的解决方案包括:

维护组状态图

我的第一个想法是创建一个可以用作地图的对象。它将使用 groupId 作为键名,状态将是值:

{
    'A': GroupState { expanded: true },
    'B': GroupState { expanded: false }
}

即使每次添加元素时都会重新创建组,但GroupState仍会持续存在。我无法解决的一个问题是如何从GroupState地图中删除不再存在的组。

例如,

vm.data(someNewArray);

与当前地图someNewArray中的项目相对应的没有 groupIds 的项目数组在哪里。GroupState我将如何删除不再有参考的条目?这似乎是我的应用程序中的内存泄漏。

这个问题可以在这个jsFiddle看到。注意,单击按钮后,组状态大小增长到 5 个元素,但只有 3 个组。这是因为尽管不再使用原始的 2 组,但并未删除。

维护一个 viewState 数组并移除计算的 observable

我的第二个想法是删除计算的 observable view。相反,我有第二个可观察的数组,称为它被聚合到当前“视图”之后viewState的数组。data然而,我很快就遇到了这个想法的问题。

首先,我必须编写几个方法来维护两个数组之间的状态:add()remove()clear()等。考虑到必须这样做,我立即开始质疑拥有第二个数组是否是个好主意。

其次,删除计算数组意味着线性搜索viewState数组以查看是否有任何当前元素包含与传入项相似的 groupId。虽然在实践中,这会非常快,但我不喜欢在每个 add ( O(n)vs O(1)) 上迭代整个数组的理论。在路上,我可能会处理成千上万的项目。


我觉得可能有一个简单的解决方案,但我正在努力解决这个问题。有没有人有任何想法可以帮助解决这个问题,或者知道用 KnockoutJS 完成这个更好的方法?也许,我上面的一个想法会增加一些洞察力(我仍然觉得GroupState地图在正确的轨道上)

如果我遗漏了一些重要信息,请告诉我,我会尽力补充

4

1 回答 1

1

这个提议怎么样:

  • 不要使用任何计算
  • 在创建新项目期间,如果该组不存在,则创建该组并将该项目添加到该组

注意:我使用 arrayFirst 来查找在创建项目期间是否存在组,它是 O(n) 但您可以将组存储在属性中,以便查找应该是 O(log(n)) (未测试)

function Item(label, groupId) {
    var self = this;
    self.label = ko.observable(label);
    self.groupId = ko.observable(groupId);
}

function Group(label) {
    var self = this;
    self.expanded = ko.observable(false);
    self.items = ko.observableArray();
    self.label = ko.observable(label);

    self.addItem = function(item) {
        self.items.push(item);
    }
}

function ViewModel() {
    var self = this;

    self.groups = ko.observableArray();

    self.addItem = function(label, groupId) {
        var group = ko.utils.arrayFirst(self.groups(), function(gr) {
            return groupId == gr.label();
        });

        if(!group) {
            console.log('not group');
            group = self.addGroup(groupId);
        }

        var item = new Item(label, groupId);
        group.addItem(item);
    }

    self.addGroup = function(groupId) {
        var group = new Group(groupId);
        this.groups.push(group);
        return group;
    }

    this.buttonPressed = function () {
        vm.addItem("Item X", "A");
    };
}

var vm = new ViewModel();
ko.applyBindings(vm);

vm.addItem("Item 1", 'A');
vm.addItem("Item 2", 'A');
vm.addItem("Item 1", 'B');
vm.addItem("Item 2", 'B');

JSFiddle 链接

于 2014-01-22T22:28:25.023 回答