Я пытаюсь выучить VueJS и столкнулся с тем, что кажется очень простой проблемой. У меня большой опыт работы с Knockout и Mustache, и я думаю, что это может вызвать у меня проблемы.
Я пытаюсь создать простой список флажков, чтобы поиграться с привязкой, но моя модель не обновляется, и я получаю следующее предупреждение в консоли:
Избегайте прямого изменения свойства, так как значение будет перезаписываться при каждом повторном рендеринге родительского компонента. Вместо этого используйте данные или вычисляемое свойство на основе значения проп. Опора изменена: "проверено"
Вот что я пытаюсь сделать;
Простая модель представления для представления элемента To Do:
function vmToDoItem(id, title){
this.id = id;
this.title = title;
this.complete = false;
}
Простая модель представления для представления всех элементов списка дел:
function vmToDo(items){
var self = this;
self.currentFilter = 'all';
self.items = items || [];
self.filters = {
all: function(){
return self.items;
},
active: function(){
return self.items.filter((item) => !item.complete);
},
complete: function(){
return self.items.filter((item) => item.complete);
}
}
}
Компонент для представления элементов To Do:
Vue.component('checkbox-item',
{
props: ['text', 'checked'],
template: '#checkbox-item-template',
computed: {
prefix: function(){
return !this.checked ? 'fa-circle' : 'fa-check-circle';
}
},
methods: {
clicked: function(){
this.checked = !this.checked;
}
}
})
Соответствующий шаблон:
<script type="text/x-template" id="checkbox-item-template">
<span @click="clicked">
<span :class="'far ' + prefix"></span>
{{ text }}
</span>
</script>
Разметка для "приложения":
<div id="app">
<checkbox-item
v-for="item in items"
class="todoItem"
:key="item.id"
v-bind:text="item.title"
v-bind:checked="item.complete">
</checkbox-item>
<br />
<span>Items unchecked: {{ remaining }}</span>
</div>
Код инициализации:
var mdl = new vmToDo();
mdl.items.push(new vmToDoItem(1, 'test 1'));
mdl.items.push(new vmToDoItem(2, 'test 2'));
var vm = new Vue({
el: '#app',
data: mdl,
computed: {
remaining: function(){
let currentFilter = this.$data.filters[this.$data.currentFilter];
return currentFilter().length;
}
}
});
Все отрисовывается очень хорошо, и щелчок по одному из элементов корректно переключает флажок, однако базовый элемент в mdl.items не обновляется, и я получаю вышеупомянутое предупреждение в консоли.
Я подозреваю, что двусторонняя привязка работает не так, как я ожидал, я ожидал, что она будет функционировать как Knockout, и я не знаю, как сделать эту функцию ожидаемой.
Для краткости приведу полный фрагмент моего выпуска:
function vmToDoItem(id, title){
this.id = id;
this.title = title;
this.complete = false;
}
function vmToDo(items){
var self = this;
self.currentFilter = 'all';
self.items = items || [];
self.filters = {
all: function(){
return self.items;
},
active: function(){
return self.items.filter((item) => !item.complete);
},
complete: function(){
return self.items.filter((item) => item.complete);
}
}
}
Vue.component('checkbox-item',
{
props: ['text', 'checked'],
template: '#checkbox-item-template',
computed: {
prefix: function(){
return !this.checked ? 'fa-circle' : 'fa-check-circle';
}
},
methods: {
clicked: function(){
this.checked = !this.checked;
}
}
})
var mdl = new vmToDo();
mdl.items.push(new vmToDoItem(1, 'test 1'));
mdl.items.push(new vmToDoItem(2, 'test 2'));
var vm = new Vue({
el: '#app',
data: mdl,
computed: {
remaining: function(){
let currentFilter = this.$data.filters[this.$data.currentFilter];
return currentFilter().length;
}
}
});
#app span.todoItem{
display: inline-block;
cursor: pointer;
padding-right: 1em;
}
#app span.todoItem span.far {
vertical-align: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.6.3/css/regular.css" />
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.6.3/css/fontawesome.css" />
<div id="app">
<checkbox-item
v-for="item in items"
class="todoItem"
:key="item.id"
v-bind:text="item.title"
v-bind:checked="item.complete">
</checkbox-item>
<br />
<span>Items unchecked: {{ remaining }}</span>
</div>
<script type="text/x-template" id="checkbox-item-template">
<span @click="clicked">
<span :class="'far ' + prefix"></span>
{{ text }}
</span>
</script>
"Подпорки, события вверх" - это правило, которое нужно запомнить. Данные передаются от родителя к ребенку через реквизит. События генерируются детьми и обрабатываются родителями (события up не являются строгим правилом, но это единственный способ, которым изменения должны идти вверх по дереву). В этом случае, вместо того, чтобы пытаться присвоить значение checked
объекту, создайте событие и поместите обработчик в компонент, чтобы родительский элемент поймал событие и действовал соответственно:
<checkbox-item v-for="item in items" class="todoItem" :key="item.id" :text="item.title" :checked="item.complete" @click="item.complete = !item.complete">
</checkbox-item>
Я изменил ваш фрагмент для работы таким образом, а также для перечисления значений item.complete, чтобы вы могли видеть, что происходит обновление данных.
function vmToDoItem(id, title) {
this.id = id;
this.title = title;
this.complete = false;
}
function vmToDo(items) {
var self = this;
self.currentFilter = 'all';
self.items = items || [];
self.filters = {
all: function() {
return self.items;
},
active: function() {
return self.items.filter((item) => !item.complete);
},
complete: function() {
return self.items.filter((item) => item.complete);
}
}
}
Vue.component('checkbox-item', {
props: ['text', 'checked'],
template: '#checkbox-item-template',
computed: {
prefix: function() {
return !this.checked ? 'fa-circle' : 'fa-check-circle';
}
},
methods: {
clicked: function() {
this.$emit('click');
}
}
})
var mdl = new vmToDo();
mdl.items.push(new vmToDoItem(1, 'test 1'));
mdl.items.push(new vmToDoItem(2, 'test 2'));
var vm = new Vue({
el: '#app',
data: mdl,
computed: {
remaining: function() {
let currentFilter = this.$data.filters[this.$data.currentFilter];
return currentFilter().length;
}
}
});
#app span.todoItem {
display: inline-block;
cursor: pointer;
padding-right: 1em;
}
#app span.todoItem span.far {
vertical-align: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.6.3/css/regular.css" />
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.6.3/css/fontawesome.css" />
<div id="app">
<checkbox-item v-for="item in items" class="todoItem" :key="item.id" :text="item.title" :checked="item.complete" @click="item.complete = !item.complete">
</checkbox-item>
<br />
<span>Items unchecked: {{ remaining }}</span>
<div v-for="item in items">
{{item.complete}}
</div>
</div>
<script type="text/x-template" id="checkbox-item-template">
<span @click="clicked">
<span :class="'far ' + prefix"></span> {{ text }}
</span>
</script>
{{ remaining }}
не реагирует
checked
это опора. Вы устанавливаете его значение в вашейclicked
функции. Это проблема. Смотрите документы по одностороннему потоку данныхthis.$root.data.items
, найти элемент там и затем обновить его?