【问题标题】:knockout mapping > mapped arrays always updated淘汰制映射 > 映射数组始终更新
【发布时间】:2014-06-07 11:47:00
【问题描述】:

这里的提琴手:http://jsfiddle.net/u9PLF/

我得到了一个有嵌套孩子的父母名单。我会定期更新整个对象层次结构并更新第一个父级的子级数组一次。我遇到的问题是,children 数组似乎总是在更新,或者至少每次我调用 fromJS 函数时它都会通知订阅者。

我希望子 observableArray 只通知订阅者一次(当它第一次从 [A,B] 更改为 [A,B,C] 时,而不是在随后的调用中)。

我做错了什么?

谢谢

代码:

var data = {
    parents: [{
        id: 1,
        name: 'Scot',
        children: ['A', 'B']
    }]
};

function ParentVM(data) {
    var self = this;

    ko.mapping.fromJS(data, {}, self);

    self.count = ko.observable(0);

    self.hello = ko.computed(function () {
        console.log("Update", self.children());
        self.count(self.count() + 1);
    });
}

var mapping = {
    parents: {
        create: function (options) {
            return new ParentVM(options.data);
        },
        key: function (data) {
            return ko.utils.unwrapObservable(data.id);
        }
    }
};

var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);

setInterval(function () {

    var data = {
        parents: [{
            id: 1,
            name: 'Scott',
            children: ['A', 'B', 'C']
        }]
    };

    ko.mapping.fromJS(data, mapping, viewModel);
}, 2000);

viewModel.parents.subscribe(function () {
    console.log("Parents notif");
});

【问题讨论】:

  • 我对映射插件不是很熟悉,所以它可能足够聪明来处理这个问题,但我猜这与 ['A','B','C'] != ['A','B','C'] 在 javascript 中的事实有关。所以天真的“这个值变了吗?”函数会认为它已经改变了。
  • 我很难过,也许忽略了一些东西,但如果你只是注释掉子部分,你会注意到完全相同的行为,而只有一个 idname父母(“父母通知”)不断在控制台中弹出。第一次当然是正确的,将名称从 Scot 更改为 Scott,但应通过 key:´ part in mapping` 阻止任何后续更改。
  • @MajorByte,我没有阅读 ko.mapping 源代码,但我的猜测是 ko.mapping 不知道 parents[0] 的内容永远不会改变。它在name('Scott') 上调用了update,即使name 没有触发更改事件,ko.mapping 也必须盲目地广播parents 的更改事件,因为它在下面的字段中执行了一些操作。
  • @huocp,似乎是这样,但仍然让我觉得很奇怪。因为parents没有改变,第一次setInterval回调执行后children也没有改变,children数组中的任何字段也没有,所以为什么需要发送一个事件数组发生了变化,但绝对没有任何变化。
  • @MajorByte,来自 ko.mapping 源代码第 590-634 行,当 mappedRootObject 是一个数组(如父母)时,它会遍历数组中的所有项目,然后调用 mappedRootObject(newContents) 而不检查newContents 的内容是否与现有内容不同。我认为这条 634 行会为父母触发更改事件。这是一个不太好的证明:jsfiddle.net/zDa3K/4 带有自定义的 ko.mapping 源代码打印在第 634 行之前和之后。

标签: javascript knockout.js knockout-mapping-plugin ko.observablearray


【解决方案1】:

@Charlie 是对的。这与 ko.mapping 无关,它是 ko.observable 测试值相等性的方式。

http://jsfiddle.net/C7wUb/

在这个演示中,每次调用items([1,2])时都会更新项目

var vm = {
    items: ko.observable([1,2]),
    count: ko.observable(0)
};

vm.items.subscribe(function(new_v) {
    console.log("items are updated to " + new_v);
    vm.count(vm.count() + 1);
});

ko.applyBindings(vm);

setInterval(function() {
    vm.items([1,2]);
}, 2000);

更深层次的原因是ko的equalComparer的默认实现,它只比较原始类型。

// src/subscribables/observable.js
ko.observable['fn'] = {
    "equalityComparer": valuesArePrimitiveAndEqual
};

// src/subscribables/extenders.js
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
    var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
    return oldValueIsPrimitive ? (a === b) : false;
}

在 observableArray 上避免不必要的更新事件的方法是覆盖equalComparer。

http://jsfiddle.net/u9PLF/2/

var oldComparer = ko.observable.fn.equalityComparer;

ko.observable.fn.equalityComparer = function(a, b) {
    var i, toStr = Object.prototype.toString;
    if (toStr.call(a) === '[object Array]' && toStr.call(b) === '[object Array]') {
        if (a.length !== b.length) return false;
        for (i = 0; i < a.length; i++) {
            if (!oldComparer(a[i], b[i])) return false;
        }
        return true;
    } else {
        return oldComparer(a, b);
    }
};

(你也可以只为children observableArray覆盖equalComparer,看看src/subscribables/extenders.js中ko.extenders notify是如何实现的)

顺便说一句,self.hello = ko.computed(...) 的做法有点冒险。

self.hello = ko.computed(function () {
    console.log("Update", self.children()); // you want to notify self.hello when children change.
    self.count(self.count() + 1); // but self.hello also has dependency on self.count()
});

我想你想要的是一个简单的subscribe,为孩子改变事件注册一个回调。

self.children.subscribe(function(new_v) {
    console.log("Update", new_v);
    self.count(self.count() + 1);
});

【讨论】:

  • 使用映射插件,您应该能够控制比较相等性,如here 所述,大约进入页面的一半,在使用“键”唯一标识对象中描述
  • ko.mapping 中的键不控制相等性,它告诉 ko.mapping 是更新现有对象还是删除现有对象然后创建新对象。
  • @MajorByte,您错过了重点,在您的 jsfiddle 中,name 永远不会触发另一个更改事件,因为'Alicw' === 'Alicw',默认情况下是 equalComparer 保证的。但是如果你不使用字符串,而是使用[1,2]作为name的值,你会看到一次又一次触发change事件,因为[1,2] !== [1,2]jsfiddle.net/sdygf/3
猜你喜欢
  • 2016-09-07
  • 2013-09-30
  • 1970-01-01
  • 2016-03-13
  • 2012-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多