复制操作后,Vue组件的顺序不正确

时间:2019-03-21 09:45:12

标签: vue.js vuejs2 vue-component

我有好几个手风琴(每个手风琴都是一个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,它必须默认扩展。但是,与此相反,新组件的所有属性值都与被复制的组​​件相同,并且列表中的最后一个组件被扩展。

我该如何解决这个问题?

提琴:https://jsfiddle.net/j3ydt1m7/

3 个答案:

答案 0 :(得分:3)

使用Vue和列表时,应使用v-for向元素添加key prop。使用这样的键,让Vue知道您的意思是特定元素。

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

我相信原因是由于性能原因,Vue默认情况下将新元素添加为最后一个元素,然后在其他节点中更新数据。因此,您添加的新元素实际上是列表中将open设置为true的最后一个元素。

答案 1 :(得分:2)

简短答案

在您的v-for循环中添加一个密钥:v-for="device in devices" :key="{something here}"。您的密钥必须是唯一的,并且即使在复制设备之后也要标识每台设备

代码

请检查:https://jsfiddle.net/Al_un/9cradxvp/。出于调试目的,我更改了几件事:

长答案

关于列表呈现

如果updated()循环中未提供密钥,则Vue不知道如何更新列表。来自Vue documentation

  

要给Vue一个提示,使其可以跟踪每个节点的身份,从而重用和重新排列现有元素,您需要为每个项目提供唯一的key属性。

让我们考虑一下您的列表(我添加了一个元素)

v-for

现在,让我们复制节点“ b”。如果没有[ {id: 1, name: "a"}, {id: 2, name: "b"}, {id: 3, name: "c"}, {id: 4, name: "d"}, ] ,则控制台输出为

:key="device.id"

对于4: d is mounted 3: c is updated 5: b_copy is updated ,控制台输出仅为:

:key="device.id"

基本上,没有按键,有:

  • 两个更新:5: b_copy is mounted 变成cb_copy变成d
  • 一个插入:c已创建

因此,每次进行复制时都会重新创建最后一个元素。由于d的默认值为open,因此显然true具有d

如果每个元素都有一个open = true,则仅创建元素:key="device.id"

要进行检查,请从我的小提琴中删除b_copy,然后查看控制台中发生的情况

选择一个键

由于密钥必须唯一地标识您的设备,因此您不应在复制设备时使用index作为密钥,因为阵列中的设备索引会随时更改

此外,最好使用ID字段,因为不能保证设备名称唯一。如果您使用

初始化列表怎么办
:key="device.id"

从功能的角度来看,这是正确的。

答案 2 :(得分:0)

代码的一些补充:

您可以直接传递索引,而不是提供“设备”对象并搜索其索引。

这是我的意思: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>