VueJS - 父对象受子

问题描述

让我们从解释结构开始。我有专用于特定公司的页面和此页面上的组件 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;
},