Неправильный порядок компонентов Vue после операции копирования - PullRequest
2 голосов
/ 21 марта 2019

У меня есть несколько аккордеонов (каждый из которых является одним компонентом Vue), и они расширены по умолчанию.Также есть функция копирования, позволяющая создавать дубликаты каждого компонента.

Vue.component("Accordion", {
  template: "#accordion-template",
  
  data: function () {
    return {
      open: true
    }
  },
  
  methods: {
    toggle: function () {
      this.open = !this.open;
    }
  }
});


new Vue({
  el: '#vue-root',
  data: {
    devices: [
      {
        name: "a", description: "first"
      },
      {
        name: "b", description: "second"
      },
      {
        name: "c", description: "third"
      }
    ]
  },

  methods: {
    copy: function (device) {
      var index = this.devices.indexOf(device) + 1;
      var copy = {
          name: device.name + "_copy",
          description: device.description + "_copy"
      };

      this.devices.splice(index, 0, copy);
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="vue-root">
    <div class="device" v-for="device in devices">
        <accordion>
            <div slot="acc-head">
                <span>{{ device.name }}</span><br/>
                <button @click="copy(device)">copy</button>
            </div>
            <div slot="acc-body">
                {{ device.description }}
            </div>
        </accordion>
    </div>
</div>

<script type="text/x-template" id="accordion-template">
    <div>
        <slot name="acc-head"></slot>
        <button @click="toggle">Open: {{ open }}</button>
        <div :class="open ? 'active' : 'hidden'">
            <slot name="acc-body"></slot>
        </div>
    </div>
</script>

Когда все аккордеоны свернуты (другими словами «open: false»), и я пытаюсь дублировать аккордеон из середины списка (например, b)Я ожидаю появления нового компонента с именем 'name'_copy, и он должен быть расширен по умолчанию.Но вместо этого новый компонент имеет те же значения всех атрибутов, что и дублированный, и последний компонент в списке расширяется.

Как я могу решить эту проблему?

Fiddle: https://jsfiddle.net/j3ydt1m7/

Ответы [ 3 ]

3 голосов
/ 21 марта 2019

При работе с Vue и списками вы должны добавить key prop к элементу с помощью v-for. Используя этот ключ, давайте узнаем, что вы имеете в виду определенный элемент.

    <div class="device" v-for="device in devices" :key="device.name">

Я полагаю, что причина этого в том, что по причинам производительности Vue по умолчанию добавляет новый элемент в качестве последнего элемента, а затем обновляет данные в других узлах. Таким образом, новый элемент, который вы добавляете, фактически является последним в списке, для которого open установлен как true.

2 голосов
/ 21 марта 2019

Краткий ответ

Добавьте ключ в цикл v-for: v-for="device in devices" :key="{something here}".Ваш ключ должен быть уникальным и идентифицировать каждое устройство, даже после копирования устройства

Код

Пожалуйста, проверьте: https://jsfiddle.net/Al_un/9cradxvp/. В целях отладки я изменил несколько вещей:

Длинный ответ

О рендеринге списка

Если ключВ цикле v-for отсутствует, Vue не знает, как обновить список.Из документации Vue :

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

Давайте рассмотрим ваш список (я добавил один элемент)

[
  {id: 1, name: "a"},
  {id: 2, name: "b"},
  {id: 3, name: "c"},
  {id: 4, name: "d"},
]

Теперь давайте скопируем узел "b".Без :key="device.id" вывод на консоль составляет

4: d is mounted
3: c is updated
5: b_copy is updated

При :key="device.id" вывод на консоль составляет только:

5: b_copy is mounted

В основном, без ключей, есть:

  • два обновления: c становится b_copy, d становится c
  • одна вставка: d создается

Следовательно, последнееЭлемент воссоздается каждый раз, когда вы переходите к копии.Так как open значение по умолчанию равно true, очевидно, что d имеет open = true.

Если каждый элемент имеет :key="device.id", то создается только элемент b_copy

Чтобы проверить это, удалите :key="device.id" из моей скрипки и посмотрите, что происходит в консоли

Выбор ключа

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

Кроме того, поле идентификатора является предпочтительным, поскольку нет гарантии, что имена ваших устройств являются уникальными.Что если вы инициализируете список с помощью

[
  { name: "a"},
  { name: "b"},
  { name: "a"}
]

С функциональной точки зрения это правильно.

0 голосов
/ 21 марта 2019

Небольшое дополнение к вашему коду:

Вместо предоставления объекта «устройство» и поиска его индекса вы можете просто передать индекс напрямую.

Вот что я имею в виду: jsFiddle

Vue.component("Accordion", {

  template: "#accordion-template",

  data: function() {
    return {
      open: true
    }
  },

  methods: {
    toggle: function() {
      this.open = !this.open;
    }
  }
});


new Vue({
  el: '#vue-root',
  data: {
    devices: [{
        name: "a",
        description: "first"
      },
      {
        name: "b",
        description: "second"
      },
      {
        name: "c",
        description: "third"
      },
    ]
  },

  methods: {
    copy: function(index) {
      var device = this.devices[index];
      var copy = {
        name: device.name + "_copy",
        description: device.description + "_copy"
      };

      this.devices.splice(index + 1, 0, copy);
    },
    remove: function(index) {
      this.devices.splice(index, 1);
    }
  }
});
.device {
  margin: 10px 0;
}

.active {
  display: block;
}

.hidden {
  display: none;
}

div.device {
  border: 1px solid #000000;
  box-shadow: 1px 1px 2px 1px #a3a3a3;
  width: 300px;
  padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-root">
  <div class="device" v-for="(device, index) in devices" :key="device.name">
    <accordion>
      <div slot="acc-head">
        <span>{{ device.name }}</span><br/>
        <button @click="copy(index)">copy</button>
        <button @click="remove(index)">remove</button>
      </div>
      <div slot="acc-body">
        {{ device.description }}
      </div>
    </accordion>
  </div>
</div>

<script type="text/x-template" id="accordion-template">
  <div>
    <slot name="acc-head"></slot>
    <button @click="toggle">Open: {{ open }}</button>
    <div :class="open ? 'active' : 'hidden'">
      <slot name="acc-body"></slot>
    </div>
  </div>
</script>
...