valueHasMutated未按预期工作

时间:2016-04-14 09:17:32

标签: javascript knockout.js

我尝试强制更新observableArray中对象值的UI更新,但数组中的对象不是observable。我假设您可以使用valueHasMutated执行此操作,但它并不像我预期的那样正常工作。

this example中,如果我点击更新按钮没有任何反应,但如果我手动重置数组,它会更新:

<div id="bindings">
  <ul data-bind="foreach: observableThings">
    <li data-bind="text: id"></li>
  </ul>
</div>
<button data-bind="click: updateValue">Update</button>
<button data-bind="click: forceUpdate">Force Update</button>
var things = [
  { id: 1, thing: false },  
  { id: 2, thing: false },
  { id: 3, thing: false },
  { id: 4, thing: false }
]

var viewModel = function() {
  var self = this;

  self.observableThings = ko.observableArray(things);

  self.updateValue = function() {
    self.observableThings()[0].id = 5;
    self.observableThings.valueHasMutated();
  }

  self.forceUpdate = function() {
    self.observableThings()[0].id = 5;
    var origThings = self.observableThings();
    self.observableThings(null);
    self.observableThings(origThings);
  }
}

ko.applyBindings(new viewModel());

valueHasMutated如何运作?

3 个答案:

答案 0 :(得分:6)

将一堆普通对象粘贴到一个可观察的数组中并不会神奇地使这些对象的属性可见。

可观察数组通常只能观察项目删除和项目插入。如果项目本身具有您想要观察的属性,则需要使这些属性显式可观察。

mapping plugin可以帮助解决这个问题。它可以做一些非常好的事情,请阅读文档页面。

function ListOfThings(params) {
  var self = this;

  self.things = ko.observableArray();

  self.updateValue = function() {
    ko.utils.arrayForEach(self.things(), function (thing) {
      thing.id( ~~(Math.random() * 100) );
    });
  }

  // init
  ko.mapping.fromJS(params, {}, self);
}


var vm = new ListOfThings({
  things: [
    { id: 1, thing: false },  
    { id: 2, thing: false },
    { id: 3, thing: false },
    { id: 4, thing: false }
  ]
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<div id="bindings">
  <ul data-bind="foreach: things">
    <li data-bind="text: id"></li>
  </ul>
</div>
<button data-bind="click: updateValue">Update</button>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

编辑您似乎在valueHasMutated上不成比例地修复,并且无法使用映射插件。映射插件很有用,但肯定没有必要。

function Thing(params) {
    this.id =  ko.observable(params.id);
    this.thing = ko.observable(params.thing);
}

function ListOfThings(params) {
  var self = this;

  self.things = ko.observableArray();

  self.updateValue = function() {
    ko.utils.arrayForEach(self.things(), function (thing) {
      thing.id( ~~(Math.random() * 100) );
    });
  }

  // init
  self.things(ko.utils.arrayMap(params.things, function (obj) {
      return new Thing(obj);
  }));
}


var vm = new ListOfThings({
  things: [
    { id: 1, thing: false },  
    { id: 2, thing: false },
    { id: 3, thing: false },
    { id: 4, thing: false }
  ]
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<div id="bindings">
  <ul data-bind="foreach: things">
    <li data-bind="text: id"></li>
  </ul>
</div>
<button data-bind="click: updateValue">Update</button>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

答案 1 :(得分:2)

KnockoutJS代码(感谢你的小提琴中的调试版),在valueHasMutated上执行导致

evaluateImmediate_CallReadWithDependencyDetection: function (notifyChange) {
// some other code
// ...

// newValue is the same as state.latestValue => "computedObservable["notifySubscribers"]" is not called
        if (computedObservable.isDifferent(state.latestValue, newValue)) {
            if (!state.isSleeping) {
                computedObservable["notifySubscribers"](state.latestValue, "beforeChange");
            }

            state.latestValue = newValue;

            if (state.isSleeping) {
                computedObservable.updateVersion();
            } else if (notifyChange) {
                computedObservable["notifySubscribers"](state.latestValue);
            }
        }

这可以解决问题:

  self.updateValue = function() {
    var index = 0;
    var item = self.observableThings()[index];
    // Update item content
    item.id = 5;
    // Force array to update markup
    self.observableThings.splice(index, 1);
    self.observableThings.splice(index, 0, item);
  }

但恕我直言,更好的方法是使用可观察的属性。

答案 2 :(得分:1)

如果您想多次更改大型observableArray的内容但只触发单个更改通知和单个UI更新等,通常会使用

valueHasMutated。因此,您将对基础数组进行操作,然后在完成所有更改后调用valueHasMutated

您对valueHasMutated的调用没有做任何事情的原因是因为可观察数组的'值'(意味着其中的项,而不是这些项的属性)没有改变。您的呼叫会导致重新评估阵列的状态 - 是否已添加或删除任何项目?答案是否定的,因此不会发生变更通知。

如果您想让代码尽可能地进行最小的更改,则需要交换新项而不是仅更新现有项的其中一个属性。

self.updateValue = function() {
    var newVal = { id: 5, thing: false };
    self.observableThings()[0] = newVal;
    self.observableThings.valueHasMutated();
  }

fiddle here

最后要说的是我上面概述的那种情况(即更新阵列中的许多成员但只触发sigle UI更新)实际上是你应该“强制”改变通知发生的唯一原因。如果你不得不做这样的事情那么你就错了并且说'但是我必须重新编写大量的代码'从来都不是不正确做法的好理由。