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

Vue中嵌套子对象无响应问题排查与解决求助

问题:Vuetify树形视图子级条目更新失效

数据结构说明

原始JSON数据结构如下:

items: {
  id: 1,
  children: [{
    id: 2,
    parentId: 1,
    children: [{
      id: 3,
      parentId: 2
    }]
  }]
}
  • level-1 条目:父级条目,parentId 均为1(根节点ID)
  • level-2 条目:子条目,parentId 对应所属 level-1 条目的ID

异常场景

使用Vuetify构建可编辑树形视图时,父级条目更新正常,但子级条目存在更新失效问题,复现步骤:

  1. 首次更新任意子级条目(正常生效)
  2. 更新该子级的父级条目(正常生效)
  3. 再次更新该子级条目(更新失效:数组数据已变更,但视图未同步)

当前更新逻辑

通过ID查找条目索引,使用 Vue.set 更新对应索引的条目,数据已更新但视图未触发重新渲染。

演示代码

JavaScript 代码

Vue.component('my-dialog', {
  template: `<v-dialog :value="dialog" persistent><v-card><v-text-field v-model="form.title"></v-text-field></v-card><v-btn @click="$emit('on-update', form)">Save</v-btn></v-dialog>`,
  props: {
    dialog: {},
    prop_item: {}
  },
  data() {
    return {
      form: {
        title: null
      }
    }
  },
  mounted() {
    Object.assign(this.form, this.prop_item);
  }
})

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data() {
    return {
      dialog: false,
      edit_item: {},
      items: {"id":1,"title":"Root","children":[{"id":2,"parentId":1,"title":"Chapter 1","children":[{"id":3,"parentId":2,"title":"Sub-chapter 1.1"},{"id":4,"parentId":2,"title":"Sub-chapter 1.2"}]},{"id":5,"parentId":1,"title":"Chapter 2","children":[{"id":6,"parentId":5,"title":"Sub-chapter 2.1"},{"id":7,"parentId":5,"title":"Sub-chapter 2.2"}]}]}
    }
  },
  methods: {
    showDialog(payload) {
      this.edit_item = payload;
      this.dialog = true;
    },
    onUpdate(payload) {
      this.dialog = false;
      // Extract the parent id
      let parent_id = payload.parentId == 1 ? payload.id : payload.parentId;
      let index = this.items.children.findIndex((item) => item.id == parent_id);
      if (index == -1) return;
      // If parent is requested to update
      if (payload.parentId == 1) {
        Vue.set(this.items.children, index, payload);
        return;
      }
      // If child is requested to update
      let childIndex = this.items.children[index].children.findIndex(
        (item) => item.id == payload.id,
      );
      if (childIndex != -1) {
        Vue.set(this.items.children[index].children, childIndex, payload);
        console.log(this.items.children[index].children[childIndex])
      }
    }
  }
})

HTML 代码

<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<head>
  <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
</head>
<div id="app">
  <v-app id="inspire">
    <div>
      <my-dialog v-if="dialog" v-model="dialog" :dialog="dialog" :prop_item="edit_item" @on-update="onUpdate"></my-dialog>
      <v-treeview :items="items.children" item-text="title">
        <template v-slot:prepend="{ item }">
          <v-icon @click.stop="showDialog(item)">mdi-pencil</v-icon>
        </template>
      </v-treeview>
    </div>
  </v-app>
</div>

问题原因

  1. 父级替换导致引用丢失:更新父级时使用 Vue.set 替换了整个父级对象,原有的子级条目引用被新对象的子级引用覆盖,TreeView内部可能仍持有旧父级引用,无法感知后续子级更新。
  2. parent_id计算逻辑错误:子级更新时,parent_id 误判为子级ID,虽然当前场景下未直接报错,但逻辑存在漏洞。

修复方案

方案1:修改父级更新逻辑,避免替换整个对象

不直接替换父级条目,而是更新其属性,保持子级引用不变:

onUpdate(payload) {
  this.dialog = false;
  // 处理父级条目(level-1)
  if (payload.parentId === 1) {
    const index = this.items.children.findIndex(item => item.id === payload.id);
    if (index === -1) return;
    // 更新属性而非替换整个对象
    Object.assign(this.items.children[index], payload);
    // 触发响应式更新
    this.items.children.splice(index, 1, this.items.children[index]);
    return;
  }

  // 处理子级条目(level-2)
  const parent = this.items.children.find(item => item.id === payload.parentId);
  if (!parent) return;
  const childIndex = parent.children.findIndex(item => item.id === payload.id);
  if (childIndex === -1) return;
  Object.assign(parent.children[childIndex], payload);
  parent.children.splice(childIndex, 1, parent.children[childIndex]);
}

方案2:通用递归查找更新(支持多层级扩展)

编写递归函数查找目标条目,直接更新属性并触发响应式:

methods: {
  // 递归查找指定ID的条目
  findItemById(items, id) {
    for (const item of items) {
      if (item.id === id) return item;
      if (item.children?.length) {
        const found = this.findItemById(item.children, id);
        if (found) return found;
      }
    }
    return null;
  },
  onUpdate(payload) {
    this.dialog = false;
    const targetItem = this.findItemById(this.items.children, payload.id);
    if (!targetItem) return;
    // 更新目标条目属性
    Object.assign(targetItem, payload);
    // 触发响应式更新
    const parent = payload.parentId === 1 ? this.items : this.findItemById(this.items.children, payload.parentId);
    const childrenArray = parent === this.items ? parent.children : parent.children;
    const index = childrenArray.findIndex(item => item.id === payload.id);
    if (index !== -1) {
      childrenArray.splice(index, 1, targetItem);
    }
  }
}

额外优化:对话框组件数据同步

修改对话框组件,确保每次打开时重新初始化表单数据:

Vue.component('my-dialog', {
  template: `<v-dialog :value="dialog" persistent><v-card><v-text-field v-model="form.title"></v-text-field></v-card><v-btn @click="$emit('on-update', form)">Save</v-btn></v-dialog>`,
  props: {
    dialog: {},
    prop_item: {}
  },
  data() {
    return {
      form: {
        title: null
      }
    }
  },
  watch: {
    prop_item: {
      immediate: true,
      handler(newItem) {
        this.form = {...newItem};
      }
    }
  }
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 06:37:57