首页 > 解决方案 > 如何在 Vue JS 观察程序中更改对象的属性而没有无限循环?

问题描述

我有一个这样的对象数组:

    let tree = [
    {
       task: "Some Task",
       spentTime : 2, 
       subTasks: {
          task: "Some Sub Task",
          spentTime: 1,
             subTasks:{
                task:"Some sub sub task",
                spentTime:30
             }
       }
    }
 ]

正如你在这里看到的,我有这种类型的树结构,我正在展示某种嵌套的手风琴。所以每个节点都有一个输入框,它有 2 路绑定与花费时间属性( using v-model )

现在,如果我输入任何节点的输入。我需要对这些花费时间值进行一些操作并重新填充或在同一对象中插入不同的值。

在这里,我正在考虑做深watch。但我认为这会导致无限循环,因为我正在更改同一个对象并重新分配值,它会watch再次触发:)

如果我想在输入更改时触发一个函数并将不同的值放回同一个对象中,我可以做什么。

谢谢!

标签: javascriptvue.jsvuejs2vue-component

解决方案


tl;博士

干净的解决方案,基于@djiss 的建议,并且正确地冒泡到最高父级,使用$setand watch,在这里: https
://codesandbox.io/s/peaceful-kilby-yqy9v 下面是最初的答案/逻辑,它使用$emit和任务“键”来移动父项中的更新。


在 Vue 中你不能直接修改子元素。我的意思是,你可以,但你不应该。当你这样做时,Vue 会警告你,通知你刚刚所做的更改将在父级更改时被覆盖。

唯一的选择是使用状态来管理你的应用程序的单一真实来源(Vuex 或简单的 Vue 对象),或者你打电话给父母告诉它:“用这个特定的值改变这个特定的孩子”。你只需倾听来自父母的变化。

这就是我在这里所做的:

const task = {
  task: "Some Task",
  spentTime: 2,
  subTasks: [{
    task: "Some Sub Task",
    spentTime: 1,
    subTasks: [{
      task: "Some sub sub task",
      spentTime: 30
    }, {
      task: "Some other sub sub task",
      spentTime: 12
    }]
  }]
};

Vue.config.productionTip = false;
Vue.config.devtools = false;
Vue.component('Task', {
  template: `
  <div>
    <h2>{{task.task}} ({{spentTime}})</h2>
    <div v-if="hasTasks">
      <Task v-for="(t, k) in task.subTasks" :key="k" :task="t" @fromChild="fromChild" :tid="k"/>
    </div>
    <input v-else v-model="localTime" type="number" @input="updateParent(localTime)">
  </div>
  `,
  props: {
    task: {
      type: Object,
      required: true
    },
    tid: {
      type: Number,
      default: 0
    }
  },
  data: () => ({
    localTime: 0
  }),
  mounted() {
    this.updateParent(this.spentTime);
  },
  computed: {
    spentTime() {
      return this.hasTasks ? this.subtasksTotal : this.task.spentTime;
    },
    subtasksTotal() {
      return this.task.subTasks.map(t => t.spentTime).reduce(this.sum, 0)
    },
    hasTasks() {
      return !!(this.task.subTasks && this.task.subTasks.length);
    }
  },
  methods: {
    fromChild(time, task) {
      this.task.subTasks[task].spentTime = time;
      this.updateParent(this.spentTime);
    },
    updateParent(time) {
      this.$emit("fromChild", Number(time), this.tid);
      this.localTime = this.spentTime;
    },
    sum: (a, b) => a + b
  },
  watch: {
    "task.spentTime": function() {
      this.localTime = this.task.spentTime;
    }
  }
});
new Vue({
  el: "#app",
  data: () => ({
    task
  }),
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <Task :task="task" :tid="0" />
</div>

只要它具有相同的结构,它就会消耗你扔给它的任何树。逻辑是:如果没有子任务则显示输入,否则从子任务计算。
显然,您可以更改它以满足您的需求。


推荐阅读