如何从主 ViewModel 更新组件内的可观察数组?

How to update an observable array inside a component from the main ViewModel?

我需要从主视图模型中设置一个 属性 在可观察数组中。

我正在使用经典的 pre 来调试和显示我的可观察数组的内容。 通过使用 types.valueHasMutated() 我可以看到应用的更改 - 仅适用于 vm.types (否则情况不会如此)。

但是,我需要看到这些更改反映在我的组件中。

在我的示例中,当我单击 "Apples" 时,相应的输入将被禁用,如下所示。可悲的是,事实并非如此。

我做错了什么?

ko.components.register("available-items", {
  viewModel: function(params) {
    function AvailableItems(params) {
      var self = this;
      self.params = params;
      self.location = "A";
      self.types = ko.computed(function() {
        var types = self.params.types();
        return ko.utils.arrayFilter(types, function(item) {
          return item.location == self.location;
        });
      });
      self.addItem = function(data, event) {
        self.params.items.addItem(self.location, data.type);
      };
    }
    return new AvailableItems(params);
  },
  template: '<div>' +
    '<h4>Add item</h4>' +
    '<ul data-bind="foreach: types">' +
    '<li>' +
    '<input type="text" data-bind="value: type, enable:available, event: {click: $parent.addItem}" readonly/>' +
    '</li>' +
    '</ul>' +
    '</div>',
  synchronous: true
});

var types = [{
  type: "Apples",
  location: "A",
  available: true
}, {
  type: "Bananas",
  location: "A",
  available: false
}];

function Vm(data) {
  var self = this;
  self.items = ko.observableArray();
  self.types = ko.observableArray(ko.utils.arrayMap(data, function(item) {
    return item;
  }));
  self.items.addItem = function(location, type) {
    self.items.push({
      location: location,
      type: type
    });
    if (location == "A" && type == "Apples") {
      self.types()[0].available = false;
      self.types.valueHasMutated();
    }
  };
}

ko.options.deferUpdates = true;
var vm = new Vm(types);
ko.applyBindings(vm);
pre {
  position: absolute;
  width: 300px;
  right: 0;
  top: 0;
}
<!DOCTYPE html>
<html>
  <head>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
  </head>
  <body>
    <div data-bind="component:{name:'available-items',params:vm}"></div>
    <ul data-bind="foreach: items">
      <li><span data-bind="text: location"></span> - <span data-bind="text: type"></span></li>
    </ul>
    <pre data-bind="text: ko.toJSON(vm.types, null, 2)"></pre>
  </body>
</html>

我找到问题了。它在您的 html 中:"params: vm" 应该是 "params: $data"

 <div data-bind="component:{name:'available-items',params:$data}"></div>

我在 jfiddle 上有 运行 这个,即使我添加了一个新类型,我也没有得到任何更新。

似乎有问题
'<ul data-bind="foreach: types">' +

我改成了

'<ul data-bind="foreach: $root.types">' +

https://jsfiddle.net/fabwoofer/9szbqhj7/1/

现在添加了类型,但似乎未处理第一项的重新呈现。有类似问题的人建议使用此处描述的模板渲染

Knockout.js Templates Foreach - force complete re-render

希望对您有所帮助

您的 可用 属性 无法观察到。为了通知 Knockout 有关更改并让它更新 UI - 使此 属性 可观察。

ko.components.register("available-items", {
  viewModel: function(params) {
    function AvailableItems(params) {
      var self = this;
      self.params = params;
      self.location = "A";
      self.types = ko.computed(function() {
        var types = self.params.types();
        return ko.utils.arrayFilter(types, function(item) {
          return item.location == self.location;
        });
      });
      self.addItem = function(data, event) {
        self.params.items.addItem(self.location, data.type);
      };
    }
    return new AvailableItems(params);
  },
  template: '<div>' +
    '<h4>Add item</h4>' +
    '<ul data-bind="foreach: types">' +
    '<li>' +
    '<input type="text" data-bind="value: type, enable:available, event: {click: $parent.addItem}" readonly/>' +
    '</li>' +
    '</ul>' +
    '</div>',
  synchronous: true
});

var types = [{
  type: "Apples",
  location: "A",
  // Make property observable
  available: ko.observable(true)
}, {
  type: "Bananas",
  location: "A",
  // Make property observable
  available: ko.observable(false)
}];

function Vm(data) {
  var self = this;
  self.items = ko.observableArray();
  self.types = ko.observableArray(ko.utils.arrayMap(data, function(item) {
    return item;
  }));
  self.items.addItem = function(location, type) {
    self.items.push({
      location: location,
      type: type,
      available: ko.observable(false)
    });
    if (location == "A" && type == "Apples") {
      // Update property as observable.
      self.types()[0].available(false);
      self.types.valueHasMutated();
    }
  };
}

ko.options.deferUpdates = true;
var vm = new Vm(types);
ko.applyBindings(vm);
pre {
  position: absolute;
  width: 300px;
  right: 0;
  top: 0;
}
<!DOCTYPE html>
<html>
  <head>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
  </head>
  <body>
    <div data-bind="component:{name:'available-items',params:vm}"></div>
    <ul data-bind="foreach: items">
      <li><span data-bind="text: location"></span> - <span data-bind="text: type"></span></li>
    </ul>
    <pre data-bind="text: ko.toJSON(vm.types, null, 2)"></pre>
  </body>
</html>

您可以使用用户 JotaBe 提供的解决方案:Refresh observableArray when items are not observables.

ko.observableArray.fn.refresh = function (item) {
    var index = this['indexOf'](item);
    if (index >= 0) {
        this.splice(index, 1);
        this.splice(index, 0, item);
    }
}

现在,我需要更改 addItem() 并使用更新后的元素添加对 refresh 的调用:

self.items.addItem = function(location, type) {
    self.items.push({
        location: location,
        type: type
    });
    if (location == "A" && type == "Apples") {
        self.types()[0].available = false;
        self.types.refresh(self.types()[0]); // <--- New sentence
    }
};

这将刷新具有 types 列表的 <pre>。但是不会刷新组件,它还有一个 types.

的列表

然后我用了这个link,Forcing deferred notifications to happen early,我在refresh里面加了ko.tasks.runEarly(),我觉得现在可以用了

ko.observableArray.fn.refresh = function (item) {
    var index = this['indexOf'](item);
    if (index >= 0) {
        this.splice(index, 1);
        ko.tasks.runEarly(); // <--- New sentence
        this.splice(index, 0, item);
    }
}

这是一个Codepen.

pauseableComputed and observable withPausing 实施的启发,我创建了 pauseableObservablepauseableObservableArray,它们能够停止向订阅者发送通知,并且比在需要时恢复。它还对所有嵌套的可暂停属性递归工作。

您可以使用它 HERE on Codepen(已根据您问题中的代码提供了示例)。 我还放置了达到目标的扩展代码:

PauseableObservable:

// PauseableObservable - it's observable that have functions to 'pause' and 'resume' notifications to subscribers (pause/resume work recursive for all pauseable child).

ko.isPauseableObservable = function(instance) {
    return ko.isObservable(instance) && instance.hasOwnProperty("pause");
}

ko.pauseableObservable = function(value) {
    var that = ko.observable(value);

    function getPauseableChildren() {
        var properties = Object.getOwnPropertyNames(that());
        var currentValue = that();
        var pauseables = properties.filter((property) => {
            return ko.isPauseableObservable(currentValue[property]);
        });
        return pauseables.map((property) => { 
            return currentValue[property];
        });
    }

    that.pauseNotifications = false;
    that.isDirty = false;

    that.notifySubscribers = function() {
        if (!that.pauseNotifications) {
            ko.subscribable.fn.notifySubscribers.apply(that, arguments);
            that.isDirty = false;
        } else {
            that.isDirty = true;
        }
    };

    that.pause = function() {    
        that.pauseNotifications = true;
        var pauseableChildren = getPauseableChildren();
        pauseableChildren.forEach((child) => { child.pause(); });
    }

    that.resume = function() {    
        that.pauseNotifications = false;

        if (that.isDirty) {
            that.valueHasMutated();
        }

        var pauseableChildren = getPauseableChildren();
        pauseableChildren.forEach((child)=> { child.resume(); });
    }

    return that;
}

PauseableObservableArray

// PauseableObservableArray - simply use pauseable functionality of his items. 
// Extension for stop notifications about added/removed items is out of scope.
ko.pauseableObservableArray = function(items) {
    var that = ko.observableArray(items);

    that.pause = function () {
        var items = that();
        items.forEach(function(item) {
            if(ko.isPauseableObservable(item)) {
                item.pause();
            }
        });
    }

    that.resume = function () {
        var items = that();
        items.forEach(function(item) {
            if(ko.isPauseableObservable(item)) {
                item.resume();
            }
        });
    }

    that.refresh = function () {
        that.resume();
        that.pause();
    }

    return that;
}

用法示例:

var firstItem = ko.pauseableObservable("Hello");
var secondItem = ko.pauseableObservable("World");
var items = [
    firstItem,
    secondItem
];
var array = ko.pauseableObservable(items);

// Stop notifications from specific observable
firstItem.pause();
// Change won't raise notification to subscribers
firstItem("Hi");
// Resume notifications
firstItem.resume();

// Stop notifications from all items of array
array.pause();
// Change won't raise notification to subscribers
array()[0]("Hi");
// Resume notifications
array.resume();

我知道实现并不完美,我也没有时间好好测试它,但我希望它能帮助你找到灵感并改进它。