You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

删除子组件后Vue列表渲染异常:仅最后一项被移除问题求助

问题描述

我将对象列表加载到子组件的v-for循环中,每个子组件配有删除按钮可移除选中项。目标子项已从数据列表中正确删除,但页面渲染时仅最后一个子组件被移除,不清楚问题所在。

  • 初始状态:页面显示多条带选择框、输入框和删除按钮的子组件行
  • 删除后异常:点击中间行的删除按钮,数据列表中对应项已删除,但页面仅最后一行消失,中间行仍保留

父组件代码

<tbody>
  <route-form-row
    v-for="(route, index) in form.current_routes"
    :key="index"
    :organizations="organizations"
    :route="route"
    :index="index"
    :completed="form.completed"
    @onUpdateOrganization="updateCurrentRouteOrganization"
    @onUpdateNumberOfRoutes="updateCurrentRouteNumberOfRoutes"
    @onRemoveRoute="removeCurrentRoute"
  ></route-form-row>
</tbody>

删除逻辑

removeCurrentRoute(index) {
  this.form.current_routes.splice(index, 1);
}

子组件代码

<template>
  <tr>
    <td>
      <select v-model="organization" :class="['form-select', {'is-invalid': organizationError}]" @change.prevent="updateOrganization()">
        <option value="">------</option>
        <option v-for="org in organizations" :key="org.id" :value="org" :selected="organization.id === org.id">{{ org.name }}</option>
      </select>
      <div v-for="error of this.v$.organization.$errors" :key="error.$uid" :class="[{'invalid-feedback': organizationError}]">{{ error.$message }}</div>
    </td>
    <td class="col-2">
      <input type="number" v-model="number_of_routes" :class="['form-control', {'is-invalid': numberOfRoutesError}]" step="1" min="0" @keyup="updateNumberOfRoutes">
      <div v-for="error of this.v$.number_of_routes.$errors" :key="error.$uid" :class="[{'invalid-feedback': numberOfRoutesError}]">{{ error.$message }}</div>
    </td>
    <td class="text-end">
      <div>
        <a href="#" @click.prevent="removeRoute()">
          <font-awesome-icon :icon="['far', 'trash-can']" />
        </a>
      </div>
    </td>
  </tr>
</template>

<script>
import { useVuelidate } from '@vuelidate/core'
import { requiredIf, helpers, minValue } from "@vuelidate/validators"

export default {
  name: 'RouteFormRow',
  props: {
    index: Number,
    route: Object,
    organizations: Array,
    completed: Boolean
  },
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      organization: this.route.organization,
      number_of_routes: this.route.number_of_routes,
    }
  },
  validations() {
    return {
      organization: { required: helpers.withMessage('Ce champ est obligatoire', requiredIf(this.completed)) },
      number_of_routes: {
        required: helpers.withMessage('Ce champ est obligatoire', requiredIf(this.completed)),
        minValue: helpers.withMessage('Assurez-vous que cette valeur est supérieure ou égale à 0.', minValue(0)),
      }
    }
  },
  computed: {
    organizationError () {return this.v$.organization.$errors.length > 0},
    numberOfRoutesError () {return this.v$.number_of_routes.$errors.length > 0},
  },
  methods: {
    updateOrganization() {
      this.$emit('onUpdateOrganization', this.index, this.organization);
    },
    updateNumberOfRoutes() {
      this.$emit('onUpdateNumberOfRoutes', this.index, this.number_of_routes)
    },
    removeRoute() {
      this.$emit('onRemoveRoute', this.index);
    }
  }
}
</script>

问题原因与修复方案

核心原因

  1. key使用错误:用数组索引作为v-for的key时,删除中间项后,后续项的索引会自动前移。Vue会复用现有组件实例,仅更新props,导致本该删除的组件未被销毁,反而最后一个因索引失效被移除。
  2. 子组件数据脱节:子组件data中的organization和number_of_routes仅在初始化时赋值,未监听route prop的变化,导致数据更新不同步。

修复步骤

1. 改用唯一标识作为v-for的key

将父组件中:key="index"改为route的唯一ID(如route.id),确保每个子组件身份唯一,Vue能正确识别需要删除的组件:

<tbody>
  <route-form-row
    v-for="(route, index) in form.current_routes"
    :key="route.id" <!-- 替换为route的唯一标识 -->
    :organizations="organizations"
    :route="route"
    :index="index"
    :completed="form.completed"
    @onUpdateOrganization="updateCurrentRouteOrganization"
    @onUpdateNumberOfRoutes="updateCurrentRouteNumberOfRoutes"
    @onRemoveRoute="removeCurrentRoute"
  ></route-form-row>
</tbody>

2. 让子组件数据响应props变化

通过watch监听route prop的变化,同步更新子组件本地数据:

// 子组件script中添加watch
watch: {
  route: {
    deep: true,
    handler(newRoute) {
      this.organization = newRoute.organization;
      this.number_of_routes = newRoute.number_of_routes;
    }
  }
}

或更推荐使用计算属性实现双向绑定,直接关联父组件数据:

// 子组件script中替换data和methods部分
computed: {
  localOrganization: {
    get() {
      return this.route.organization;
    },
    set(val) {
      this.$emit('onUpdateOrganization', this.index, val);
    }
  },
  localNumberOfRoutes: {
    get() {
      return this.route.number_of_routes;
    },
    set(val) {
      this.$emit('onUpdateNumberOfRoutes', this.index, val);
    }
  }
}

同时修改模板中的v-model:

<select v-model="localOrganization" ...>...</select>
<input type="number" v-model="localNumberOfRoutes" ...>

3. 可选:确保数据响应式

如果form.current_routes是嵌套对象,需保证其本身是Vue响应式数据(Vue2用Vue.set,Vue3用ref/reactive),避免修改后页面不更新。


内容的提问来源于stack exchange,提问作者Kornikopic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:56:02