Как обновить DOM, когда в объекте, отображаемом v-for, происходят изменения?

Я использую v-for для отображения списка на основе массива объектов.

<ul>
  <li v-for="category, i in categories"
  :class="{active: category.active}"
  @click="changeCategory(i)">
    <a>{{ category.service_type_name }}</a>
  </li>
</ul>

При щелчке по элементу списка changeCategory(index) запускается:

changeCategory(index) {
    this.categories.forEach((c, i) => {
        c.active = (i != index)? false : true
    })
}

Таким образом, атрибут active элементов списка, по которым щелкнули клики, получает значение true, а все остальные - false, а элемент списка получает добавленный к нему класс .active (из-за строки :class="{active: category.active}" в шаблоне.

Однако модель DOM не обновляется, чтобы отображать это изменение.

Есть ли способ автоматически обновлять DOM, когда происходит это изменение, без использования this.$forceUpdate() в функции changeCategory(index)?

edit: изменено map на forEach, DOM по-прежнему не обновляется.

изменить: я использую компоненты vue-class с машинописным текстом

import Vue from 'app/vueExt'
import { Component } from 'vue-property-decorator'
import * as Template from './services.vue'

@Component({
    mixins: [Template]
})
export default class Services extends Vue {
    categories = []

    created() {
        this.api.getServiceSetupCatagories().then((res) => {
            this.categories = res.categories
            this.categories.forEach((c, i) => {
                // adding active property to the object
                // active = true if i = 0, false otherwise
                c.active = (i)? false : true
            })
        })
    }

    changeCategory(index) {
        this.categories.forEach((c, i) => {
            c.active = (i != index)? false : true
        })
    }
}

person wrahim    schedule 01.05.2017    source источник
comment
map не изменяет ваш массив, он возвращает новый массив. Поэтому vue не обнаруживает никаких изменений, и ваш dom не обновляется. Попробуйте вместо этого использовать forEach.   -  person Eric Guan    schedule 01.05.2017
comment
@EricGuan, это правда, хороший аргумент. Я попробовал forEach, и событие дошло до использования цикла for для изменения свойства active, но это не дало результата. Когда я console.log элемент, он показывает правильное active значение, но DOM не обновляется.   -  person wrahim    schedule 01.05.2017
comment
Кажется, у меня работает. codepen.io/Kradek/pen/MmmZLN   -  person Bert    schedule 01.05.2017
comment
@BertEvans Да, я не совсем понимаю, что происходит. Я пробовал ваш код с функцией map вместо forEach, и он тоже работает, как задумано. Я не уверен, почему в моем случае DOM не обновляется.   -  person wrahim    schedule 01.05.2017
comment
Технически да, карта будет работать и в этом случае, вы просто неправильно ее используете. С помощью карты предполагается, что вы возвращаете новый массив, а не просто выполняете итерацию по массиву. У каждой категории заранее есть свойство active?   -  person Bert    schedule 01.05.2017
comment
Если свойство active не существует на category до тех пор, пока не будет вызван метод changeCategory, тогда вы попадаете в предостережение в Vue и должны использовать Vue.set(c, "active", i != index ? false : true). vuejs.org/v2/guide/reactivity.html#Change-Detection- Предостережения   -  person Bert    schedule 01.05.2017
comment
@BertEvans Раньше не было. Но теперь я добавляю к объекту свойство active перед рендерингом (в ловушке created()). По-прежнему никакого эффекта.   -  person wrahim    schedule 01.05.2017
comment
Как упоминает @RoyJ, нам нужно увидеть больше кода, чтобы помочь.   -  person Bert    schedule 01.05.2017
comment
Позвольте нам продолжить это обсуждение в чате.   -  person wrahim    schedule 01.05.2017


Ответы (2)


После обсуждения вопроса я предложил следующий альтернативный метод.

Добавьте activeIndex в data и измените шаблон на следующий.

<ul>
  <li v-for="category, i in categories"
      :class="{active: activeIndex === i}"
      @click="activeIndex = i">
    <a>{{ category.service_type_name }}</a>
  </li>
</ul>

Похоже, это работает для @wrahim.

Я считаю, что основная проблема с исходным кодом заключалась в том, что свойство active было добавлено к category, попадающему в один из Vue предостережения при обнаружении изменений.

person Bert    schedule 01.05.2017

Это работает, как ожидалось. Ваша проблема не в коде, который вы разместили здесь. (Даже при использовании map: внутри карты объекты являются ссылками, поэтому изменение их членов приводит к изменению исходного объекта.)

new Vue({
  el: '#app',
  data: {
    categories: [{
        service_type_name: 'one',
        active: false
      },
      {
        service_type_name: 'two',
        active: false
      }
    ]
  },
  methods: {
    changeCategory(index) {
      this.categories.map((c, i) => {
        c.active = (i != index) ? false : true
      })
    }
  }
});
.active {
  background-color: lightgray;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script>
<ul id="app">
  <li v-for="category, i in categories" :class="{active: category.active}" @click="changeCategory(i)">
    <a>{{ category.service_type_name }}</a>
  </li>
</ul>

person Roy J    schedule 01.05.2017