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构建可编辑树形视图时,父级条目更新正常,但子级条目存在更新失效问题,复现步骤:
- 首次更新任意子级条目(正常生效)
- 更新该子级的父级条目(正常生效)
- 再次更新该子级条目(更新失效:数组数据已变更,但视图未同步)
当前更新逻辑
通过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>
问题原因
- 父级替换导致引用丢失:更新父级时使用
Vue.set替换了整个父级对象,原有的子级条目引用被新对象的子级引用覆盖,TreeView内部可能仍持有旧父级引用,无法感知后续子级更新。 - 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
相关产品推荐
相关产品推荐

