简体   繁体   English

为什么我的数据属性被 Vuejs 中的计算属性更新?

[英]Why is my data property being updated by a computed property in Vuejs?

I have a computed property ( filteredMeasurementsByAttribute ) that is supposed to provide a filtered version of a data property ( measurementsByAttribute ).我有一个计算属性( filteredMeasurementsByAttribute ),它应该提供数据属性( measurementByAttribute )的过滤版本。 However, the changes on the computed property are also taking place on the data property.但是,计算属性的更改也发生在数据属性上。

The filter is using the data from selectedAttributes (which are the options selected in drop downs on the front end) and then returning the matching data in measurementsByAttribute .过滤器使用来自selectedAttributes的数据(这是在前端的下拉列表中选择的选项),然后在measurementByAttribute 中返回匹配的数据。 This filter is working properly.此过滤器工作正常。 The issue is that when I run the clearAttribute method which clears the data from selectedAttributes (this part works successfully) the measurementsByAttribute property is the filtered version so I can't get the old data back.问题是,当我运行clearAttribute方法,它会清除selectedAttributes数据(这部分工作的成功)的measurementsByAttribute属性是过滤后的版本,所以我不能让旧数据了。

In short, I want to keep an original version of the measurementsByAttribute property so that I clear the selectedAttributes property and have all the original data available for filteredMeasurementsByAttribute to reset the drop down forms.简而言之,我想保留measurementByAttribute属性的原始版本,以便清除selectedAttributes属性并拥有所有原始数据可用于filteredMeasurementsByAttribute以重置下拉表单。

I've tried saving the data in a regular javascript variable, measurementsByAttributeMaster , and then setting the measurementsByAttribute to the master.我试图保存在一个常规的JavaScript变量,measurementsByAttributeMaster数据,然后将measurementsByAttribute设置到主。 Somehow they all end up just having the filtered values.不知何故,它们最终都只有过滤后的值。

I've tried changing the way that I'm looping through the data (eg using forEach instead of filter or map) just to see if that was causing it to edit the original data.我已经尝试改变我循环数据的方式(例如使用 forEach 而不是过滤器或地图),只是为了查看是否会导致它编辑原始数据。 No luck.没运气。 I've left the "original" versions using the filter and map in commented out code so you can see both versions.我在注释掉的代码中使用过滤器和映射保留了“原始”版本,因此您可以看到两个版本。

Any insight or help is very much appreciated.非常感谢任何见解或帮助。

/**
 * Select Product
 */

function filterMeasurements(attribute, measurementsAvailable) {
    var filteredMeasurements = [];

    attribute.measurements.forEach(function(measurement) {
        if (measurementsAvailable.includes(measurement.id.toString())) {
            filteredMeasurements.push(measurement);
        }
    });
    // return attribute.measurements.filter(function(measurement) {
    //     return measurementsAvailable.includes(measurement.id.toString());
    // });

    return filteredMeasurements;
}

/**
 * Check if the part matches the selected Attributes
 *
 * @param {array} selectedAttributes Array of all the currently selected attributes
 * @param {object} part The part we are checking
 * @retrun {array} the fitlered selectedAttributes
 */
function checkPartAttributes(selectedAttributes, part) {

    var partAttributes = JSON.parse(part.attributes_json);

    // Loop through each selected attribute to ensure there aren't any conflicts
    return selectedAttributes.every(function(measurementID, attributeID) {

        // if no measurement has been selected, it is acceptable
        if (measurementID == "") {
            return true;
        }

        // if the part does have this attribute, it needs to match
        if (attributeID in partAttributes) {
            return partAttributes[attributeID] == measurementID;
        }

        console.log('here');

        // If the part doesn't have this attribute, it is acceptable
        return true;

    });

}

// var SelectSpecifications = require('./components/SelectProduct/SelectSpecifications.vue');

var vm = new Vue({
    el: '#select-product',
    components: {
        SelectSpecifications
    },
    data () {
        return {
            allParts: allParts,
            measurementsByAttribute: measurementsByAttribute,
            selectedAttributes: selectedAttributes,      
        }
    },
    computed: {
        /** 
         * combine all the attributes from the matching parts
         *
         * @return {array} The filtered attributes 
         */
        filteredAttributes: function() {
            var filteredAttributes = JSON.parse("{}");
            console.log('filteredAttributes');

            // loop through each matching part and create new array of the matching attributes
            this.matchingParts.forEach(function(part) {

                var partAttributes = JSON.parse(part.attributes_json);

                for (attributeID in partAttributes) {

                    // Add to the index if already exists
                    if (attributeID in filteredAttributes) {

                        filteredAttributes[attributeID].push(partAttributes[attributeID]);
                        filteredAttributes[attributeID] = [...new Set(filteredAttributes[attributeID])];

                    // create the index if it doesn't already exist
                    } else {

                        var tempArr = [partAttributes[attributeID]];
                        filteredAttributes[attributeID] = tempArr;

                    }

                }
            });

            return filteredAttributes;
        },

        /**
         * filter the measurements by selected values
         * @return {object} All the filtered measurements sorted by attribute
         */
        filteredMeasurementsByAttribute: {
            get: function() {

                console.log('filteredMeasurementsByAttribute');

                var selected = this.selectedAttributes;
                var filteredMeasurementsByAttribute = [];
                var filteredAttributesKeys = Object.keys(this.filteredAttributes);
                var filteredAttributes = this.filteredAttributes;

                this.measurementsByAttribute.forEach(function (attribute, index) {

                    console.log(attribute);
                    var tempAttribute = attribute;
                    // filteredMeasurementsByAttribute[index] = tempAttribute;

                    if (filteredAttributesKeys.includes(tempAttribute.id.toString())) {

                        var filteredMeasurements = filterMeasurements(tempAttribute, filteredAttributes[tempAttribute.id]);                    
                        tempAttribute.measurements = filteredMeasurements;
                        filteredMeasurementsByAttribute[index] = tempAttribute;

                    }

                });

                // return measurementsByAttribute.map(function(attribute) {

                //     if (filteredAttributesKeys.includes(attribute.id.toString())) {

                //         attribute.measurements = filterMeasurements(attribute, this.filteredAttributes[attribute.id]);
                //         return attribute;

                //     } 

                // }, this);

                return filteredMeasurementsByAttribute;

            },
            set: function(newMeasurementsByAttribute) {
                console.log('setter working!');
                this.measurementsByAttribute = newMeasurementsByAttribute;
            }
        },

        // returns matching parts depending on what attributes are selected
        matchingParts: function() {
            console.log('matchingParts');
            return this.allParts.filter(checkPartAttributes.bind(this, this.selectedAttributes));
        },
    },
    methods: {
        clearAttribute: function(attributeID) {
            this.$set(this.selectedAttributes, attributeID, "");
            var tempArray = [];
            this.filteredMeasurementsByAttribute = tempArray.concat(measurementsByAttributeMaster);

            // this.selectedAttributes.splice(0);
        },
        kebabTitle: function(title) {

            if (title == null) {
                return '';
            }

            return title.replace(/([a-z])([A-Z])/g, "$1-$2")
             .replace(/\s+/g, '-')
             .toLowerCase();
        },        
    }
});

In JavaScript, setting a variable to an object only copies the reference to the object.在 JavaScript 中,为对象设置变量只会复制对对象的引用 In data() , you've initialized this.measurementsByAttribute to a local variable named measurementsByAttribute , so any changes to this.measurementsByAttribute would affect the original variable.data() ,您已将this.measurementsByAttribute初始化为名为measurementsByAttribute的局部变量,因此对this.measurementsByAttribute任何更改都会影响原始变量。

The problem is observed in the getter for filteredMeasurementsByAttribute , which modifies this.measurementsByAttribute and thus the original variable:filteredMeasurementsByAttribute的getter中观察到了这个问题,它修改了this.measurementsByAttribute ,从而修改了原始变量:

filteredMeasurementsByAttribute: {
  get: function() {
    this.measurementsByAttribute.forEach(function (attribute, index) {
        // tempAttribute refers to original object in `this.measurementsByAttribute`
        var tempAttribute = attribute;

        if (filteredAttributesKeys.includes(tempAttribute.id.toString())) {
            // ❌ this changes original object's prop
            tempAttribute.measurements = filteredMeasurements;
        }
    });

    //...
  }
}

One solution would be to:一种解决方案是:

  1. Apply Array.prototype.filter() to this.measurementsByAttribute in order to get the measurements that match the filter selection.Array.prototype.filter()应用于this.measurementsByAttribute以获得与过滤器选择匹配的测量值。
  2. and use Array.prototype.map() and the spread operator to shallow-clone the original objects with their measurements prop modified.并使用Array.prototype.map()扩展运算符来浅克隆原始对象,并修改其measurements属性。
filteredMeasurementsByAttribute: {
  get: function() {
    const filteredAttributesKeys = Object.keys(this.filteredAttributes)

    return this.measurementsByAttribute
      /* 1 */ .filter(m => filteredAttributesKeys.includes(m.id.toString()))
      /* 2 */ .map(m => ({
                ...m,
                measurements: filterMeasurements(m, this.filteredAttributes[m.id])
              }))
  }
}

You are initiating the change to the original attribute when the computed prop is changed.当计算道具更改时,您正在启动对原始属性的更改。

// ...
filteredMeasurementsByAttribute: {
// ...

 set: function(newMeasurementsByAttribute) {
                console.log('setter working!');
                this.measurementsByAttribute = newMeasurementsByAttribute;
            }
        },
//...

I would just have the compute prop apply filter and return the result, but do not have any set on the compute.我只会让计算道具应用过滤器并返回结果,但在计算上没有任何set

If you want the original attribute but have reactive attributes everywhere in the component, it may help to use a different variable.如果您想要原始属性但在组件中的任何地方都有反应属性,则使用不同的变量可能会有所帮助。

const measurementsByAttributeClone = {...measurementsByAttribute};

// any changes to `measurementsByAttribute` will not impact `measurementsByAttributeClone`

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM