Vue嵌套数据:删除父节点后,子组件不会隐藏

时间:2019-05-08 12:07:54

标签: javascript vue.js

我让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>

2 个答案:

答案 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);
}