Как изменить свойства объекта, не имея бесконечного l oop в Vue JS наблюдателя? - PullRequest
1 голос
/ 30 марта 2020

У меня есть Массив Объектов, подобный этому:

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

Как вы можете видеть здесь, у меня есть этот тип древовидной структуры, и я показываю что-то вроде вложенного аккордеона. Таким образом, каждый узел имеет поле ввода, которое имеет двухстороннюю привязку со свойством lostTime ( using v-model ).

Теперь, если я введу любой из входных данных узла. Мне нужно сделать некоторые операции с этими значениями потраченного времени и заново заполнить или вставить разные значения в один и тот же объект.

Здесь я думал о глубоком watch. Но я думаю, что это приведет к бесконечному l oop, потому что я изменяю тот же объект и присваиваю значение обратно, и это снова вызывает watch :)

Что я могу сделать, если я хочу вызвать функцию на входе изменить и положить разные значения обратно в один и тот же объект.

Спасибо!

Ответы [ 3 ]

1 голос
/ 31 марта 2020

У меня были похожие проблемы с реактивностью с Vue. js Попробуйте использовать Vue .set или this. $ Set для сохранения любых изменений в вашем массиве:

this.$set(this.someObject, 'b', 2)
  • You Вы можете узнать больше о Vue set здесь .
  • Вы можете прочитать больше информации о Vue реактивности здесь .
1 голос
/ 31 марта 2020

tl; dr

Чистое решение, основанное на предложении @djiss, которое правильно всплывает до верхнего предка, используя $set и watch, здесь: https://codesandbox.io/s/peaceful-kilby-yqy9v
Ниже приведен начальный ответ / logi c, в котором используются $emit и «ключ» задачи для перемещения обновления в родительском элементе.


В Vue вы не можете изменить ребенка напрямую. Я имею в виду, вы можете , но вы не должны . Когда вы делаете это, Vue предупреждает вас об этом, сообщая вам, что только что внесенное вами изменение будет отменено, как только изменится родитель.

Единственные варианты - использовать состояние для управления единственным источником trouth для ваше приложение (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>

Оно будет потреблять любое дерево, которое вы кидаете в него, при условии, что оно имеет такую ​​же структуру. Логика c: показать входные данные, если нет подзадач, или рассчитать из подзадач иначе.
Очевидно, вы можете изменить это в соответствии со своими потребностями.

0 голосов
/ 31 марта 2020

Я столкнулся с этой головной болью раньше, и я решил / обманул ее с глубокими watch и Loda sh _.cloneDeep и _.isEqual.

Внутри вашего дочернего компонента создайте свои собственные данные componentTask. Вы будете watch вашим componentTask и вашим prop. Каждый раз, когда они меняются, сравнивайте их, используя _.isEqual. Когда componentTask изменяется, отправляет событие своему родителю.

Подзадача:

<template>
    <div>
        <input type="text" v-model="componentTask.task">
        <input type="number" min="0" v-model.number="componentTask.spentTime">
        <SubTask v-if="task.subTasks" @task-change="handleTaskChange" :task="task.subTasks" />
    </div>
</template>

<script lang="ts">
    import {Vue, Component, Prop, Watch} from 'vue-property-decorator'
    import {Task} from "@/components/Test/Test";
    import _ from "lodash";

    @Component
    export default class SubTask extends Vue {
        @Prop() task!: Task;

        componentTask: Task | undefined = this.task;

        @Watch('task', {deep: true, immediate: true})
        onTaskChange(val: Task, oldVal: Task) {
            if (_.isEqual(this.componentTask, val))
                return;

            this.componentTask = _.cloneDeep(val);
        }

        @Watch('componentTask', {deep: true, immediate: true})
        onComponentTaskChange(val: Task, oldVal: Task) {
            if (_.isEqual(val, this.task))
                return;

            this.$emit("task-change");
        }

        handleTaskChange(subTasks: Task){
            this.componentTask = subTasks;
        }
    }
</script>

Родительский класс:

<template>
    <div style="margin-top: 400px">
        <h1>Parent Task</h1>
        <br>
        <div style="display: flex;">
            <div style="width: 200px">
                <h4>task</h4>
                <p>{{task.task}}</p>
                <p>{{task.spentTime}}</p>
                <br>
            </div>
            <div style="width: 200px">
                <h4>task.subTasks</h4>
                <p>{{task.subTasks.task}}</p>
                <p>{{task.subTasks.spentTime}}</p>
                <br>
            </div>
            <div style="width: 200px">
                <h4>task.subTasks.subTasks</h4>
                <p>{{task.subTasks.subTasks.task}}</p>
                <p>{{task.subTasks.subTasks.spentTime}}</p>
                <br>
            </div>
        </div>
            <SubTask :task="task" @task-change="handleTaskChange"/>
    </div>
</template>

<script lang="ts">
    import {Vue, Component, Prop} from 'vue-property-decorator'
    import SubTask from "@/components/Test/SubTask.vue";
    import {defaultTask, Task} from "@/components/Test/Test";

    @Component({
        components: {SubTask}
    })
    export default class Test extends Vue {
        task: Task = defaultTask;

        handleTaskChange(task: Task) {
            this.task = task;
        }
    }

</script>

Определенный интерфейс:

export interface Task {
    task: string;
    spentTime: number;
    subTasks?: Task;
}

export const defaultTask: Task = {
    task: "Some Task",
    spentTime : 2,
    subTasks: {
        task: "Some Sub Task",
        spentTime: 1,
        subTasks:{
            task:"Some sub sub task",
            spentTime:30
        }
    }
};
...