VueJS - Просмотр моделей, детей и обновление значений

1

Я пытаюсь выучить 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>
  • 1
    checked это опора. Вы устанавливаете его значение в вашей clicked функции. Это проблема. Смотрите документы по одностороннему потоку данных
  • 0
    Привет @RoyJ, спасибо за ответ. Я перечитывал эту часть документации и теперь понимаю, что это односторонняя связка, однако, как кто-то может добиться этого? в основном, я хочу, чтобы элемент управления 'checkbox-item' обновлял значение соответствующей модели при нажатии на нее. Тогда я смогу повторно использовать этот компонент для любого элемента типа флажка, который я хочу. Нужно ли мне получить доступ к this.$root.data.items , найти элемент там и затем обновить его?
Теги:
vue.js

1 ответ

0

"Подпорки, события вверх" - это правило, которое нужно запомнить. Данные передаются от родителя к ребенку через реквизит. События генерируются детьми и обрабатываются родителями (события 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>
  • 0
    {{ remaining }} не реагирует
  • 1
    Я знаю, но это не было частью вопроса, и я не хотел это выяснять.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню