Изменение данных в дочернем компоненте влечет изменение в родительском
Недавно начал изучать Vue.js, возможно просто не знаю чего-то, суть проблемы:
В App.vue имеется массив folders: [], который передается в дочерний компонент Home.vue. В Home.vue, в свою очередь, создается локальная копия данного массива: localFolders: [...this.folders], после чего над локальным массивом могут происходить некоторые действия(добавление, удаление, изменение элемента массива), для каждого из этих действий у меня есть дополнительный компонент, куда я передаю локальный массив из Home.vue, затем таким же образом в этих дочерних компонентах создаю локальные копии для возможности отката действий, однако тут возникает проблема, при изменении локальной копии массива в, скажем, EditFolder.vue, тут же происходят изменения и в локальном массиве Home.vue. Проблема даже в том, что по логике моей программы, измененные данные отправляются из EditFolder.vue в Home.vue, а потом и в App.vue только после нажатия кнопки, однако изменения в локальном массиве в Home.vue происходят еще до нажатия данной кнопки, то есть, сразу при изменении данных в EditFolder.vue они тут же изменяются и в Home.vue...
Данные для folders: [], в App.vue приходят при загрузке страницы.
Возможно что-то упускаю из-за неопытности, буду благодарен за помощь!
App.vue
<template>
<div>
...
<router-view
@edit_Folder="editFolder($event)"
:folders="folders"
/>
...
</div>
</template>
<script>
...
export default {
...
data() {
...
folders: [],
...
};
},
methods: {
...
async editFolder(changedFolderData) {
const uid = await this.getUid();
await firebase
.database()
.ref(
`/users/${uid}/folders/${
this.folders[changedFolderData.index].data.name
}`
)
.remove();
await firebase
.database()
.ref(`/users/${uid}/folders/${changedFolderData.folder.data.name}`)
.set({ ...changedFolderData.folder });
await this.getFolders();
},
...
},
</script>
Home.vue
<template>
<div>
<div
v-for="(folder, index) in localFolders"
:key="index"
>
...
<EditFolder
:index="index"
:folder="folder"
@editFolder="edit_Folder($event, index)"
/>
...
</div>
</div>
</template>
<script>
...
export default {
...
props: {
folders: {
type: Array,
default: () => [],
},
},
data() {
return {
...
localFolders: [...this.folders],
...
};
},
methods: {
...
edit_Folder(changedFolder, index) {
// проверка на совпадение имени измененной папки
if (
this.localFolders.filter(
(el, elIndex) =>
elIndex != index && el.data.name === changedFolder.data.name
).length === 0
) {
this.localFolders[index] = { ...changedFolder };
changedFolder.data.show_settings = false;
changedFolder.data.settings.showEditForm = false;
this.$emit("edit_Folder", { folder: changedFolder, index });
}
},
...
},
EditFolder.vue
<template>
<div>
<input
v-model.trim="localFolder.data.name"
/>
<button @click.left="editFolder">...</button>
</div>
</template>
<script>
...
export default {
...
props: {
folder: Object,
},
data() {
return {
...
localFolder: { ...this.folder },
...
};
},
methods: {
...
editFolder() {
if (this.localFolder.data.name) {
this.$emit("editFolder", this.localFolder);
}
},
...
},
Ответы (1 шт):
Прежде всего хотелось бы заметить что это особенность JS, а не vue.
Разберем проблему:
// создаем оригинальный массив *ссылок* на объекты.
const originArray = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Pineapple" },
{ id: 4, name: "Watermelon" }
];
// созадем копию массива *ссылок* на объекты
const oneLevelCopy = [...originArray];
// так как мы изменяем объект по той же самой ссылке,
// он изменится в обоих массивах
oneLevelCopy[0].id = 0;
// но если мы пытаемся изменить массив, например
// добавив новое значение, оно добавится только
// в новый массив, так как сами массивы разные
oneLevelCopy.push({ id: 5, name: "Kiwi" })
Что бы решить вашу проблему нужно создать глубокую копию массива, то есть массив и внутренняя вложенность должны быть скопированы.
Самый простой и не особо эффективный (по скорости) метод:
const deepCopy = JSON.parse(JSON.stringify(originArray));
Вначале мы превращаем объект в строку, а затем из строки снова далем объектом, в таком случае у ссылок нет не единого шанса. Однако есть и ограничения. В объекте нельзя использовать все что не может быть сериализовано, например циклические ссылки.
В вашем случае это скорее всего не проблема так как от Firebase данные однозначно приходят в JSON, а значит как минимум раз они уже были сериализованы.
Но все же стоит это помнить. Я бы порекомендовал использовать более надежные средства например lodash/cloneDeep.
Еще один совет не относящийся к вопросу, не совершайте запросов и изменения данных во Vue, отведите ему роль средства отвечающего за отображения данных хранящихся и изменяемых в Vuex. А запросы вынесите в сервисы, и обращайтесь к ним только из Vuex. Чуть подробнее про это можно прочитать в этом ответе.