【问题标题】:KnockoutJS - UI not updating with built-in observableArray methods except push and popKnockoutJS - UI 不使用内置 observableArray 方法更新,除了 push 和 pop
【发布时间】:2015-10-16 12:36:21
【问题描述】:

当我对我的 observable 数组执行 push 或 pop 操作时,它会反映在 ui.xml 中。然而,数组上的其他操作不会改变 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 方面,我将地址声明为可观察数组

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();

有效(使用新的/删除的元素更新 UI)。但是addresses.splice(0, 1, newAddressObject) 不再在 UI 中做任何事情。

我在这里缺少什么?如何推动流行工作而不是其他工作? 我是否遇到了淘汰赛框架中的错误?

更新

我找到了一种方法,但是出了点问题。我会先说到这里:

我很清楚,如果我在可观察数组中使用可观察对象,更改将反映在 UI 中。然而这正是我想要避免的事情。这是一种矫枉过正。

在属性真正暴露给用户交互的情况下,应该需要可观察的属性。例如,如果您有一个用于设置对象的每个字段的 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);

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

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

【问题讨论】:

  • 相信你的答案就在这里:stackoverflow.com/questions/14953248/…
  • 观察的是数组,而不是单个元素。
  • @jason9187 我不是在更新单个元素,而是在更新整个数组。请查看我在问题中的更新
  • 您的最后一个示例可能无法正常工作,因为您正在尝试拼接已在数组中的相同对象。 KO 可能不会将此解释为对数组的更改。
  • @jason9187 是的,这正是我所说的,ko 不会将其解释为一个新对象,尽管它的字段完全不同。所以我实际上正在考虑在调用var tmp = addresses() 时创建一个真正的新数组,例如new Array(addresses())

标签: javascript html knockout.js template-engine


【解决方案1】:

您错误地将值分配给可观察的 title,这就是 UI 未反映其更改的原因(2 路绑定损坏)。

Thumb 规则总是使用() 表示法,同时为可观察对象赋值(保持双向绑定不变

视图模型:

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

【讨论】:

  • observableArray 中的对象未声明为可观察对象。我明确希望避免将它们声明为可观察对象。这就是为什么我的语法在更新标题时没有包含()。请检查我在问题中所做的更新。我只是想一次更新整个数组。它在某些情况下是成功的,而在其他情况下则不会起作用
【解决方案2】:

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

为了实现我想要的,我们必须解决像in this post这样的javascript中的深度克隆问题。

现在有一个权衡,如果您的对象中没有循环架构或功能,深度克隆在原版中非常简单。就我而言,没有这样的事情。数据来自一个 RESTful API。如果将来有人遇到这个问题,他们需要深度克隆他们的“难以克隆”的对象。

这是我的解决方案:

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,我在一个示例中演示了这两种方法

【讨论】:

    猜你喜欢
    • 2012-04-04
    • 2013-02-02
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 2016-10-21
    • 2013-06-17
    • 2021-12-06
    相关资源
    最近更新 更多