Компонент нокаута с наблюдаемым объектом не обновляет данные
У меня есть следующий компонент:
<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
, Это установит поля каждого объекта в массиве в наблюдаемые, чтобы их можно было передать в привязку компонента.