问题描述
让我们从解释结构开始。我有专用于特定公司的页面和此页面上的组件 Classification.vue
,它显示分配给当前公司的标签类别和标签本身。首先,我使用 axios get 请求获取所有可能的类别,然后获取分配给当前公司的所有标签,最后将标签映射到相应的类别。这是Classification.vue
:
import DoughnutChart from "@comp/Charts/DoughnutChart";
import ModalDialog from '@comp/ModalDialog/ModalDialog';
const EditForm = () => import('./EditForm');
export default {
components: {
DoughnutChart,ModalDialog,EditForm
},props: ['companyData'],async created() {
const companyLabels = await this.$axios.get('/companies/' + this.companyData.id + '/labels');
const allLabelsCategories = await this.$axios.get('/labels/categories');
allLabelsCategories.data.map(cat => {
this.$set(this.labelsCategories,cat.labelCategoryId,{...cat});
this.$set(this.labelsCategories[cat.labelCategoryId],'chosenLabels',[]);
});
companyLabels.data.map(l => {
this.labelsCategories[l.label.labelCategory.labelCategoryId].chosenLabels.push({...l.label,percentage: l.percentage})
});
},computed: {
portfolioChartData() {
let portfolios = [];
// 35 id stands for 'Portfolio' labels category
if (this.labelsCategories[35] !== undefined && this.labelsCategories[35].chosenLabels !== undefined) {
this.labelsCategories[35].chosenLabels.map(label => {
portfolios.push({label: label.name,value: label.percentage});
});
}
return portfolios;
},portfolioLabels() {
let portfolios = [];
// 35 id stands for Portfolio labels category
if (this.labelsCategories[35] !== undefined && this.labelsCategories[35].chosenLabels !== undefined) {
return this.labelsCategories[35].chosenLabels;
}
return portfolios;
}
},data() {
return {
labelsCategories: {}
}
}
}
到目前为止一切顺利,我得到了对象 labelsCategories
,其中键是类别的 id,值是类别对象,它们现在也有 chosenLabels
键,我们在 created()
中设置了它。正如您所看到的,我使用了计算属性,它们对于“投资组合”类别的图表是必要的。我在 created() 中使用 $set 方法正是为了触发 labelsCategories
对象的反应性,因此计算属性可以分别对此做出反应。
现在我在 Classification.vue
- EditForm.vue
中有一个新组件,它是动态导入的。在这个组件中,我做了几乎相同的事情,但现在我需要为每个类别获取所有可能的标签,而不仅仅是分配。所以我像这样传递道具:
<modal-dialog :is-visible="isFormActive" @hideModal="isFormActive = false">
<EditForm v-if="isFormActive" ref="editForm" :labels-categories-prop="{...labelsCategories}" />
</modal-dialog>
而 EditForm
组件如下所示:
export default {
name: "EditForm",props: {
labelsCategoriesProp: {
type: Object,required: true,default: () => ({})
}
},created() {
this.labelsCategories = Object.assign({},this.labelsCategoriesProp);
},async mounted() {
let labels = await this.$axios.get('/labels/list');
labels.data.map(label => {
if (this.labelsCategories[label.labelCategoryId].labels === undefined) {
this.$set(this.labelsCategories[label.labelCategoryId],'labels',[]);
}
this.labelsCategories[label.labelCategoryId].labels.push({...label});
});
},data() {
return {
labelsCategories: {}
}
}
}
现在问题来了。每当我使用 EditFrom
组件打开模态窗口时,Calssification.vue
中的计算属性都会被触发,并且图表正在动画化并更改数据。为什么?一个很好的问题,在挖掘了一下之后我注意到,在 EditForm
组件中我也使用了 $set,如果我将使用 $set 添加一些虚拟值,例如:
this.$set(this.labelsCategories[label.labelCategoryId],['dummy']);
它会覆盖父组件中的labelsCategories值(Classification.vue
)
怎么可能?正如您所看到的,我尝试将 prop 作为 {...labelsCategories} 传递,甚至做了 this.labelsCategorie = Object.assign({},this.labelsCategoriesProp);
但我的父对象仍然受到 child.我通过 ===
和 'Object.is()' 比较了 EditForm 组件中的 prop 和 labelsCategories 对象,它们不一样,所以我完全困惑。任何帮助都受到高度赞赏。
顺便说一句,我可以通过将 prop 作为 :labels-categories-prop="JSON.parse(JSON.stringify(labelsCategories))"
传递来解决这个问题,但这对我来说似乎是一个黑客。
解决方法
好的,我在这个问题上进行了更深入的挖掘并了解到,{...labelsCategories}
和 Object.assign({},this.labelsCategoriesProp)
都不会创建对象的深层副本,只是浅层副本。所以,我想这就是问题的原因。在本文中,我了解了对象的浅拷贝和深拷贝:https://medium.com/javascript-in-plain-english/how-to-deep-copy-objects-and-arrays-in-javascript-7c911359b089
所以,我可以使用 JSON.parse(JSON.stringify(labelsCategories))
离开我的老套路,或者我可以使用诸如 lodash 之类的库:
_.cloneDeep(labelsCategories)
但根据文章我也可以创建自定义方法。而这个选项非常适合我。我已经有一个用于处理对象的 vue mixin,所以我只是在那里添加了 deepCopy() 函数:
deepCopy(obj) {
let outObject,value,key;
if (typeof obj !== "object" || obj === null) {
return obj; // Return the value if obj is not an object
}
// Create an array or object to hold the values
outObject = Array.isArray(obj) ? [] : {};
for (key in obj) {
value = obj[key];
// Recursively (deep) copy for nested objects,including arrays
outObject[key] = this.deepCopy(value);
}
return outObject;
},