KnockoutJS - UI 除了 push 和 pop 之外,不使用内置的 observableArray 方法更新

KnockoutJS - UI not updating with built-in observableArray methods except push and pop

当我在我的可观察数组上执行推送或弹出操作时,它反映在 ui 中。然而,对数组的其他操作不会改变 UI 中的任何内容。这是我的案例示例:

<ul data-bind="foreach: addresses">
     <!-- ko template: {name: 'AddressItemTemplate', data: {address: $data, page: 'update-page'} }-->
     <!-- /ko -->
</ul>

我在两个不同的页面中使用我的模板,这就是我这样使用模板数据的原因。

<script type="text/html" id="AddressItemTemplate">
    <p data-bind="text: (page == 'update-page') ? 'updating' : 'declined'"</p>
    <p data-bind="text: address.title"></p>
</script>

现在在 js 方面,ofc 我将地址声明为可观察数组

this.addresses = ko.observableArray([addresObject1, addressObject2, ...])

在页面的某处,我编辑了地址值。要让 UI 反映更改,我执行以下操作:

//suppose we know that the first address is being edited
var tmp_addresses = addresses();
tmp_addresses[0].title = 'blabla';
addresses(tmp_addresses);

就是这样,在 viewModel 中,我可以看到 addresses 的内容已更新,但 UI??

中没有
addresses.push(someAddressObject);

addresses.pop();

有效(使用 new/removed 元素更新 UI)。但是 addresses.splice(0, 1, newAddressObject) 不会再对 UI 做任何事情。

我在这里错过了什么?如何推动流行音乐而不是其他人? 我是否遇到了 Knockout 框架的错误?

更新

我找到了一种方法,但是有问题。我会谈到这一点,但首先:

我很清楚,如果我在 observable 数组中使用 observable 对象,更改将反映在 UI 中。然而,这正是我想要避免的事情。这是一个大材小用。

在属性真正暴露给用户交互的情况下,可观察属性应该是 required。例如,如果您有一个 UI 用于设置对象的每个字段,那么是的,observable 属性 将是正确的调用。

但是在我的例子中,我什至没有 UI 来更新地址字段。此外,我不需要修补和不断观察所有地址的所有属性。在我的例子中,服务器不时发生更新,并且只更改单个地址字段中的单个字段。

从另一个角度来看我建议的方法应该可行。我只是一次更新整个数组,而不是单独更新每个元素。这与以下逻辑完全相同:

someObservableObject({newObject: withNewFields, ...});

这就是为什么我不需要我的对象作为可观察对象。我只是想重新声明数组并完成更改。例如,建议如果您要对可观察数组进行大量推送,不要多次使用 array.push(...),而是重新声明更大的数组到可观察数组变量 以与我在问题中类似的方式进行。否则,我会告诉 knockout 跟踪每个对象及其中的每个字段,这几乎不是我想要的。

现在,我终于让它工作了,但我的做法表明有一种更简洁的方法。

我发现,当您用它们重新声明数组时,可观察数组中的项目会以某种方式被跟踪并且不会更新。例如,我在问题中给出的代码将不起作用。但是下面的代码有效:

var tmp_addresses = addresses();
var tmp_addr = tmp_addresses[0];
var new_addr = {};
Object.keys(tmp_addr).forEach(function(key){
    new_addr[key] = tmp_addr[key];
});
new_addr.title = 'Hey this is something new!'
addresses.splice(0, 1, new_addr);

不满意?下面的代码也可以正常工作,因为我们正在重新定义数组:

var newAddressObject1 = {...}, newAddressObject2 = {...};
addresses([newAddressObject1, newAddressObject2]);

但是下面的不行!

var tmp_addresses = addresses();
var tmp_addr = tmp_addresses[0];
tmp_addr.title = 'Hey this address wont update';
addresses.splice(0, 1, tmp_addr);

怎么会?我认为 knockout 在他的 observableArrays 项目中添加了一个内部 属性,当我尝试重新插入一个时,它不会更新。

我的问题现在已经演变为创建一个 new 对象,该对象具有与可观察数组中所需项目相同的属性。我上面的编码方式看起来很脏。一定有更好的方法来做到这一点

您错误地为可观察对象 title 赋值,这就是 UI 未反映其更改的原因(2 种方式绑定已损坏)。

拇指法则 在给可观察对象赋值时总是使用() 表示法(保持双向绑定完整

viewModel:

var ViewModel = function () {
    var self = this;
    self.addresses = ko.observableArray([{
        'title': ko.observable('one')
    }, {
        'title': ko.observable('two')
    }])
    setTimeout(function () {
    var tmp_addresses = self.addresses();
    tmp_addresses[0].title('blabla'); //assigning data to observable 
    self.addresses(tmp_addresses);
    }, 2000)
};
ko.applyBindings(new ViewModel());

工作样本here

PS: 当您使用 = 完成分配时,不要被 viewModel 中的值变化所欺骗 [=] 两个绑定被破坏 UI 不会反映 VM 的更改。

当你 splice 启动你的 observableArray UI 时,它会更改检查 here

问题正如@jason9187 在评论中指出的那样:当我编辑对象的字段时,可观察数组中对象的引用没有改变。因此,KO 不会将我的数组解释为已更改。如果 observableArray 包含简单的数据类型,那么我建议的方法就可以毫无问题地工作。但是,我在数组中有一个Object,所以虽然我编辑了Object,它的引用(指针)还是一样的,KO认为所有的Object都和以前一样。

为了达到我想要的效果,我们必须解决 javascript 中的 深度克隆 问题,例如 in this post.

现在需要权衡取舍,如果您的对象中没有循环架构或功能,深度克隆在原版中非常简单。就我而言,没有那样的事情。数据来自restfulAPI。如果将来有人解决了这个问题,他们需要深度克隆他们的 'hard-to-clone' 对象。

这是我的解决方案:

var tmp_addresses = JSON.parse(JSON.stringify(addresses())); //Creates a new array with new references and data
tmp_addresses[0].title = 'my new title';
addresses(tmp_addresses);

或者,如果您可以创建地址对象,以下方法也可以:

var tmp_addresses = addresses();
tmp_addresses[0] = new randomAddressObject(); 
addresses(tmp_addresses);

这是一个 fiddle 我在一个示例中演示了这两种方法