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: показать входные данные, если нет подзадач, или рассчитать из подзадач иначе.
Очевидно, вы можете изменить это в соответствии со своими потребностями.