Компонент нокаута с наблюдаемым объектом не обновляет данные

У меня есть следующий компонент:

<template id="fruits-tpl">
    <p>Name: <input data-bind="value: name" /></p>
    <p>Type: <input data-bind="value: color" /></p>
</template>


ko.components.register('fruits', {
    viewModel: function(params) {
        this.name = params.name;
        this.color   = params.color;
    },
    template: { element: 'fruits-tpl' }
});

Я использую этот компонент вместе с моделью представления ниже, где элементы в моем наблюдаемом списке имеют разные типы и имеют разные свойства:

function Fruit(data) {
    this.name = ko.observable(data.name);
    this.color = ko.observable(data.color);
}
function Dessert(data) {
    this.name = ko.observable(data.name);
    this.packaging = ko.observable(data.packaging);
}
function Vm(){
    var data = [{name:"Apples",color:"Yellow"},{name:"Cookies",packaging:"Box"}];
    this.items = ko.observableArray([new Fruit(data[0]),new Dessert(data[1])]);
    this.items.choice = ko.observable(this.items()[0]);
}

Этот компонент работает очень хорошо, и базовые данные обновляются каждый раз, когда я меняю текст в моих полях ввода:

<div data-bind="component: {name: 'fruits', params: items.choice}"></div>

Теперь я хотел бы включить логику моих наблюдаемых в сам компонент, поэтому я изменил компонент следующим образом:

ko.components.register('fruits', {
    viewModel: function(params) {
        this.name = ko.observable(params.name);
        this.color   = ko.observable(params.color);
    },
    template: { element: 'fruits-tpl' }
});

... и теперь у меня есть мой наблюдаемый items.choice только с данными внутри:

function Vm(){
    var data = [{name:"Apples",color:"Yellow"},{name:"Cookies",packaging:"Box"}];
    this.items = ko.observableArray(data);
    this.items.choice = ko.observable(this.items()[0]);
}

Почему базовые данные в основной модели представления не обновляются во втором примере, хотя items.choice все еще можно наблюдать? Я уверен, что мне не хватает некоторых концепций, может быть, каждый элемент в моём наблюдаемом массиве должен быть также наблюдаемым, но я не понимаю, есть ли способ заставить работать второй пример.

Первый пример: http://jsfiddle.net/5739ht0q/2/ Второй пример: http://jsfiddle.net/079tx0nn/

2 ответа

Решение

Есть несколько способов обновить данные до модели основного вида, которую я показал ниже.

Но сначала давайте немного доработаем модель основного вида.

function Vm(data) {
  var self = this;
  self.items = ko.observableArray(ko.utils.arrayMap(data, function(item) {
    return ko.observable(item);
  }));
  self.items.choice = ko.observable(0);
  self.items.choice.data = ko.computed(function() {
    return self.items()[self.items.choice()];
  });
}

Быстро и грязно

Первый быстрый и грязный способ - передать компоненту функцию, которая была определена внутри модели основного вида:

<div data-bind="component: {
    name: 'fruits',
    params: {index: items.choice(), data: items.choice.data(), update: items.update}
}"></div>

Внутри компонента мы можем вызвать функцию для сохранения изменений:

self.data = ko.computed(function(){
    params.update(params.index, ko.toJS(self));
});

Функция обновления в модели основного вида теперь очевидна, не нужно тратить на это больше слов.

Использование подписки

Второй способ - использовать подписку для установления связи по моделям представлений:

ko.intramodels = new ko.subscribable();

Из компонента отправьте уведомление:

self.data = ko.computed(function(){
  ko.intramodels.notifySubscribers(ko.toJS(self), "updateFruits");
});

Подписка внутри модели основного вида будет получать и сохранять изменения, более или менее примерно такие:

  ko.intramodels.subscribe(function(newValue) {
    self.items.replace(self.items()[self.items().index], newValue);
  }, self, "updateFruits");

Конечно, это может быть сделано вручную, как описано выше, но библиотека почтовых ящиков великого Райана Нимейера была бы оптимальным выбором здесь: https://github.com/rniemeyer/knockout-postbox.

Я протестировал оба решения, но, к сожалению, у меня возникли некоторые проблемы, когда я активировал опцию отсроченного обновления - new in knockout 3.4: ko.options.deferUpdates = true; как я получил ошибку "Превышен максимальный размер стека вызовов".

Удалить круговую зависимость

Потому что я не хочу сдаваться и пропустить это новое замечательное повышение производительности нокаута 3.4, и потому что эта ошибка также означает более или менее,

Круговые зависимости являются ошибкой проектирования, пожалуйста, переосмыслите некоторую часть вашей реализации,

Я изменил модель представления, чтобы цепочка отслеживания зависимостей работала только в одном направлении, но использовала только одну наблюдаемую для всех данных компонента:

ko.components.register('fruits', {
  viewModel: function(params) {
    var self = this;
    self.data = params.peek();
    self.item = {};
    self.item.name = ko.observable(self.data.name);
    self.item.color = ko.observable(self.data.color);
    self.update = ko.computed(function() {
       params(ko.toJS(self.item));
    });
  },
  template: {
    element: 'fruits-tpl'
  }
});

Это намного более очевидно с вложенными компонентами, которые имеют всю обработку данных и наблюдаемое создание внутри них, и модели основного представления не нужно ничего знать о том, что находится внутри дочерних элементов и почему - просто передавать и получать назад наблюдаемый объект:

<div data-bind="component:{name:'fruits',params:items.choice.data}"></div>

<template id="containers-tpl">
  <div data-bind="foreach: containers">
    <p><input data-bind="textInput: quantity"><span data-bind="text: name"></span></p>
  </div>
</template>

<template id="fruits-tpl">
  <p>Name:<input data-bind="textInput: item.name"></p>
  <p>Color:<input data-bind="textInput: item.color"></p>
  <div data-bind="component:{name:'containers',params:item.containers}"</div>
</template>

Ключевые моменты здесь:

  • передать наблюдаемое компоненту, а не только данные, и
  • разорвать цепочку зависимостей параметров внутри компонента.

Полная скрипка с вложенными компонентами: http://jsfiddle.net/jo37q7uL/

Observables находятся в основе нокаута и позволяют двухстороннее связывание между view и ViewModel. Для обновления поля JSON компонент должен изменить наблюдаемое, к которому у "VM" есть доступ.

Чтобы избежать определения функций Fruit и Dessert, вы можете использовать отображение Knockout для преобразования массива данных в ko.observableArray, Это установит поля каждого объекта в массиве в наблюдаемые, чтобы их можно было передать в привязку компонента.

Другие вопросы по тегам