Vue递归组件数据变更后视图不显示问题排查
Vue递归组件修改二维数组后视图不更新的解决方法
问题根源
- 违反props单向数据流规则:子组件直接修改传入的
dirprops,Vue中props是父向子单向传递的,子组件直接修改会导致更新无法同步到父组件,也触发不了响应式更新。 - Vue2数组响应式局限:直接通过索引修改数组元素(比如
this.dir[0][0] = !this.dir[0][0]),Vue2的响应式系统无法检测到这种操作,自然不会触发视图更新。
解决步骤
1. 子组件通过事件通知父组件更新数据
子组件不再直接修改dir,而是通过$emit触发父组件的方法,由父组件负责更新数据,严格遵循单向数据流规则。
2. 使用Vue.set触发数组响应式更新
父组件中更新二维数组时,用this.$set(或Vue.set)修改数组元素,这样Vue就能检测到变化并同步更新视图。
修改后的完整代码
<html> <body> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <div class="container"> <h4> Vue.js Expandable Tree Menu<br /> <small>(Recursive Components)</small> </h4> <div id="app"> <tree-menu :nodes="tree.nodes" :depth="0" :label="tree.label" :dir="dir" @toggle="handleToggle"></tree-menu> </div> </div> <script> let tree = { label: "root", nodes: [ { label: "item1", nodes: [ { label: "item1.1", }, { label: "item1.2", nodes: [ { label: "item1.2.1", }, ], }, ], }, { label: "item2", }, ], }; Vue.component("tree-menu", { props: ["nodes", "label", "depth", "dir"], template: ` <div > <div class="label-wrapper" @click="toggleChildren"> <div :style="indent" :class="labelClasses"> <i v-if="nodes" class="fa" :class="iconClasses"></i> {{ label }} </div> </div> <tree-menu v-if="dir[0][0]" v-for="node in nodes" :nodes="node.nodes" :label="node.label" :depth="depth + 1" :dir="dir" @toggle="handleToggle" > </tree-menu> </div>`, data() { return { showChildren: false, }; }, computed: { iconClasses() { return { "fa-plus-square-o": !this.showChildren, "fa-minus-square-o": this.showChildren, }; }, labelClasses() { return { "has-children": this.nodes }; }, indent() { return { transform: `translate(${this.depth * 50}px)` }; }, }, methods: { toggleChildren() { // 触发父组件的toggle事件,传递要修改的数组索引 this.$emit('toggle', 0, 0); this.showChildren = !this.showChildren; }, handleToggle(row, col) { // 向上传递事件,确保所有层级的点击都能通知到父组件 this.$emit('toggle', row, col); } }, }); new Vue({ el: "#app", data: { tree, dir: [ [1, 1, 1, 1, 1], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], ], }, methods: { handleToggle(row, col) { // 使用$set修改二维数组元素,触发响应式更新 const newValue = !this.dir[row][col]; this.$set(this.dir[row], col, newValue); alert(newValue); } } }); </script> <style> body { font-family: "Open Sans", sans-serif; font-size: 18px; font-weight: 300; line-height: 1em; } .container { width: 300px; margin: 0 auto; } .tree-menu { .label-wrapper { padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid #ccc; .has-children { cursor: pointer; } } } </style> </body> </html>
关键修改说明
- 子组件的
toggleChildren方法不再直接修改dir,而是通过$emit把修改需求传递给父组件。 - 父组件用
this.$set修改二维数组元素,确保Vue能检测到变化并更新视图。 - 递归的子组件绑定
@toggle事件并向上传递,保证所有层级的点击操作都能触发父组件的数据更新。
注:如果你的需求是让每个节点对应dir中不同的状态,而非所有节点共用dir[0][0],可以在触发事件时传递当前节点对应的索引,让每个节点控制自身的显示状态,这部分可根据实际需求调整。
内容的提问来源于stack exchange,提问作者artur nowakowski
相关产品推荐
相关产品推荐

