我让Vue根据根实例中的数据source
显示组件的一些副本。数据是带有子项的嵌套JS对象。加载数据后,将添加指向父节点的指针。在视图中,我可以向上和向下钻取,一次显示一个层次结构。视图中不使用递归。
我的问题是:为什么子组件在删除其父节点时不消失?
请忽略冗长的CSS:它是大型项目的一部分,与手头的问题无关。
由于SO片段无法很好地显示,因此这里是一个CodePen:https://codepen.io/MSCAU/pen/RmWOWE。困难之处在于,它仍然不允许您启动Vue DevTools并在后台进行窥视,因此我将尝试进一步简化或获得独立页面。
var source = {
"name": "root",
"value": 9,
"id": 0,
"children": [{
"name": "Nodes",
"value": 32,
"id": 100,
"children": [{
"name": "Fish",
"value": 20,
"id": 1,
"children": [{
"name": "Cod",
"value": 5,
"id": 10,
},{
"name": "Salmon",
"value": 15,
"id": 110,
}]
}, {
"name": "Drinks",
"value": 12,
"id": 3,
"children": [{
"name": "Juice",
"value": 8,
"id": 11,
},
{
"name": "Wine",
"value": 4,
"id": 12,
}]
}]
}]
};
function clone(obj) {
/* Standard clone function */
if(obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
return obj;
var temp = obj.constructor(); // changed
for(var key in obj) {
if(Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = clone(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
Vue.component('bar-wrapper', {
props: ['node', 'index', 'side'],
template: '#bar-wrapper',
methods: {
amount(node) {
// bar.value = bar.old_value / 100 * this.root;
return Math.abs(node.value / node.parent.value) * 100;
},
drillDown(side, node, event) {
this.$emit('drill-to', side, node);
}
}
});
var app = new Vue({
el: '#main',
data: {
db: {
tree: clone(source)
},
expenses: {
children: []
}
},
computed: {
expenses_view_nodes() {
return this.expenses.children;
}
},
methods: {
amount(node) {
return Math.abs(node.value / node.parent.value) * 100;
},
compare(a, b) {
if (Math.abs(a.value) < Math.abs(b.value))
return 1;
if (Math.abs(a.value) > Math.abs(b.value))
return -1;
return 0;
},
makeParents(node) {
var _this = this;
if (!node.hasOwnProperty("children"))
return false;
else {
var parent_node = node;
$.each(node.children, function() {
this.parent = parent_node;
_this.makeParents(this);
});
}
return node;
},
sortMe() {
if (this.expenses && this.expenses.children) {
this.expenses.children.sort(this.compare);
}
return false;
},
drillDown(side, node) {
this.expenses = node;
},
drillUp(side, node, event) {
if (node.parent && node.parent.id != 0) {
this.expenses = node.parent;
}
},
insertCake() {
this.db.tree.children[0].children.push({id: Math.floor(Math.random() * 10000), name: "Cake", value: 14, parent: this.db.tree.children[0]});
this.db.tree.children[0].value += 14;
},
deleteFish() {
var this_amount = this.db.tree.children[0].children[0].value;
this.db.tree.children[0].children.splice(0,1);
this.db.tree.children[0].value -= this_amount;
},
deleteChild() {
// Vue.delete(this.db.tree.children[0].children[0].children, 0);
var this_amount = this.db.tree.children[0].children[0].children[0].value;
this.db.tree.children[0].children[0].children.splice(0,1);
this.db.tree.children[0].children[0].value -= this_amount;
this.db.tree.children[0].value -= this_amount;
},
init() {
this.db.tree = clone(source);
this.expenses = this.db.tree.children[0];
this.makeParents(this.db.tree);
this.sortMe();
}
},
watch: {
db: {
handler(newVal, oldVal) {
console.log("Watched the db");
},
deep: true
}
},
beforeCreate() {
console.log("%cBefore create hook: Component is not created and data variables are not available yet", "color:green");
},
created() {
console.log("%cCreated hook: Component is created and data variables are available", "color:green");
this.init();
},
beforeMount() {
console.log("%cBefore mount hook: Component is not mounted on DOM yet", "color:green");
},
mounted() {
console.log("%cMounted hook: Component is mounted on DOM", "color:green");
},
beforeUpdate() {
console.log("%cBefore update hook: Component is not updated yet", "color:green");
},
updated() {
console.log("%cUpdated hook: Component is updated", "color:green");
},
beforeDestroy() {
console.log("%cBefore destroy hook: Component is about to be destroyed", "color:green");
},
destroyed() {
console.log("%cDestroyed hook: Component is destroyed", "color:green");
}
});
a {
color: #659B5E;
}
a:hover, a:focus {
color: #3c5c37;
}
.btn {
font-size: 12px;
letter-spacing: 0.5px;
padding: 6px 18px;
text-transform: uppercase; }
.wrapper {
position: relative;
width: 100%;
}
.charts {
position: relative;
top: 0%;
text-align: center;
vertical-align: middle;
cursor: move;
transition: all 1s;
padding: 0px 10px 0px;
margin-bottom: 20px; }
.chart {
position: relative;
}
.chart-left {
float: left;
margin-right: 0.5%; }
.chart-right {
float: right;
margin-left: 0.5%; }
.bar-wrapper {
position: relative; }
.bar {
position: relative;
padding: 5px 0px;
margin-bottom: 30px;
height: 34px;
font-size: 1.2em;
opacity: 1;
-webkit-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.2);
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.2); }
.bar.parent {
cursor: pointer; }
.bar.parent .bar-label > a:after {
content: '\02026'; }
.bar:not(.parent) {
cursor: default; }
.chart-left .bar-wrapper {
transform-origin: right center;
height: 64px;
}
.chart-right .bar-wrapper {
transform-origin: left center;
height: 64px;
}
.bar-label {
position: absolute;
white-space: nowrap;
overflow: visible;
width: 100%;
pointer-events: none; }
.bar-label > a {
color: white;
display: inline-block;
max-width: 100%;
transition: transform 0.2s, color 0.2s, text-shadow 0.2s;
pointer-events: auto;
text-decoration: none; }
.bar-label > a:hover {
text-decoration: underline; }
.bar-label > a.no-description {
text-decoration: none;
cursor: default; }
.bar-label .popover-footer {
padding: 9px 14px 9px;
font-size: 12px;
background-color: #f8f8f8;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas: "type edit"; }
.bar-label .popover-footer .node-type {
grid-area: type;
color: #bbb;
text-transform: uppercase; }
.bar-label .popover-footer .node-type .fa, .bar-label .popover-footer .node-type .fas, .bar-label .popover-footer .node-type .far {
margin-right: 10px; }
.bar-label .popover-footer .node-edit {
grid-area: edit;
text-align: right; }
.bar-label .popover-footer .node-edit a {
pointer-events: auto;
display: none; }
.chart-left .bar-fluid .bar-label {
left: 0%;
padding-left: 22px; }
.chart-left .bar-fixed .bar-label {
left: 0%;
padding-left: 12px; }
.chart-left .compare {
right: 0%;
background-color: #e0e0e0;
padding-left: 12px;
text-align: left;
border-right: 1px solid #f8f8f8;
margin-right: 0;
margin-left: auto; }
.chart-left .bar-label .popover {
left: -40px !important;
right: auto !important; }
.chart-left .bar-label > a {
text-align: left;
text-shadow: -3px 0px 4px #f46d43, 3px 0px 4px #f46d43, 0px 3px 4px #f46d43, 0px -3px 4px #f46d43; }
.chart-left .bar-label > a.outside {
text-align: right; }
.charts .bar-label > a.outside {
text-shadow: none;
color: #999; }
.chart-left .bar {
background: linear-gradient(#f14813 4%, #f46d43 5%, #f14813 95%, #f79273 96%);
background-position: left top;
border-right: none;
text-align: left;
margin-left: auto;
margin-right: 0px;
overflow: visible !important; }
.chart-left .bar:hover .sub-bar {
background-color: rgba(0, 0, 0, 0.15);
box-shadow: inset 0 0 2px #ffffff; }
.chart-left bar.bar-fluid {
background: linear-gradient(#f14813 4%, #f46d43 5%, #f14813 95%, #f79273 96%); }
.chart-left .bar-fixed {
background: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2) 10px, transparent 10px, transparent 20px), linear-gradient(#f14813 4%, #f46d43 5%, #f14813 95%, #f79273 96%); }
.bar-fixed .bar-handle {
display: none; }
.chart-left .bar.hiding {
-webkit-animation: remove-left 0.5s ease forwards;
/* Chrome, Safari, Opera */
animation: remove-left 0.5s ease forwards; }
.chart-left .bar.showing {
-webkit-animation: add-left 0.5s ease forwards;
/* Chrome, Safari, Opera */
animation: add-left 0.5s ease forwards; }
.amount, .charts .percentage {
position: absolute;
white-space: nowrap; }
.chart-left .bar-fixed .amount, .chart-left .bar-fixed .percentage {
text-align: right;
right: 100%;
padding-right: 10px; }
.chart-left .bar-fluid .amount, .chart-left .bar-fluid .percentage, .chart-left .compare .amount, .chart-left .compare .percentage {
text-align: right;
right: 100%;
padding-right: 22px; }
.chart-right .bar-fixed .amount, .chart-right .bar-fixed .percentage {
text-align: left;
left: 100%;
padding-left: 10px; }
.chart-right .bar-fluid .amount, .chart-right .bar-fluid .percentage, .chart-right .compare .amount, .chart-right .compare .percentage {
text-align: left;
left: 100%;
padding-left: 22px; }
.shadowed {
box-shadow: 1px 2px 5px 1px rgba(0, 0, 0, 0.1); }
.chart-left .line, .chart-left .sub-bar {
right: 0px;
border-left: 1px solid rgba(255, 255, 255, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.2); }
.chart-right .line, .chart-right .sub-bar {
left: 0px;
border-left: 1px solid rgba(255, 255, 255, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.2); }
.sub-bar {
height: 34px;
position: absolute;
top: 0px;
opacity: 1;
text-align: center; }
.sub-bar span {
display: none; }
.chart-left .sub-bar {
right: 0px;
}
.chart-right .sub-bar {
left: 0px;
}
body {
margin: 50px;
}
.chart {
float: none;
cursor: pointer;
border: 1px solid lightgrey;
background-color: #eee;
}
.bars {
height: 300px;
}
.bar {
height: 34px;
margin-bottom: 26px;
}
.sub-bar {
display: inline-block;
}
.list-move {
transition: transform 1s;
}
.tools {
margin: 30px auto;
text-align: center;
}
.tools button {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/x-template" id="bar-wrapper">
<div>
<div class="bar bar-fluid" v-bind:style="{display: 'flex', 'flex-direction': 'row-reverse', 'padding': 0, transition: 'none', width: amount(node) + '%'}">
<div class="sub-bar" v-for="(no, index) in node.children" v-bind:key="no.id" v-bind:style="{position: 'static', width: amount(no) + '%'}"></div>
<div class="bar-label" style="top: 5px">
<a v-if="node.children" @click.stop.prevent="drillDown(side, node, $event)">{{node.name}}...</a>
<span v-else>{{node.name}}</span>
</div>
</div>
</div>
</script>
<div id="main">
<center>
<p>
<i>Click on a bar label to drill down. Click on the chart background to come back up.</i>
</p>
<b>
<p>
<u>How to reproduce:</u> Click on <kbd>Fish...</kbd>, then click on <kbd>Delete Fish</kbd> button.
</p>
<p>
<u>Question:</u> Why does the view not update when I am looking at Cod and Salmon (inside Fish), and I delete the Fish node?
</p>
</b>
</center>
<div class="tools">
<button @click="deleteFish">Delete Fish</button>
<button @click="deleteChild">Delete first child</button>
<button @click="insertCake">Insert Cake</button>
<button @click="sortMe">Sort</button>
<button @click="init">Reset</button>
</div>
<div class="charts">
<div class="chart chart-left" @click="drillUp(0, expenses, $event)">
<transition-group name="list" tag="div" class="bars">
<bar-wrapper v-on:drill-to="drillDown" v-for="(node, index) in expenses_view_nodes" :key="node.id" class="wrapper" :node="node" :index="index" :side="0" :style="{position: 'absolute', top: index * 60 + 'px'}"></bar-wrapper>
</transition-group>
</div>
</div>
</div>
答案 0 :(得分:0)
在JS中,从数组中拼接元素不会将其从内存中删除,因此即使节点已从父级的子级数组中删除,我的expenses_view_nodes
仍然有效。
我解决此问题的方法是-删除Fish节点后-检查视图指针(expenses_view_nodes
)是否指向仍在树中的节点。如果不是,请将其更改为指向Fish的父节点(id = 100
)。然后Vue会按预期重画。
答案 1 :(得分:0)
在Vue中从数组中删除项目时,请尝试在.splice
中包装实际的setTimeout
操作。
而不是类似的东西:
del: function(item) {
this.list.splice(this.list.indexOf(item), 1);
}
执行以下操作:
del: function(item) {
var list = this.list, index = list.indexOf(item);
setTimeout(function() { list.splice(index, 1); }, 0);
}