【问题标题】:Why is my data property being updated by a computed property in Vuejs?为什么我的数据属性被 Vuejs 中的计算属性更新?
【发布时间】:2020-09-21 16:19:55
【问题描述】:

我有一个计算属性 (filteredMeasurementsByAttribute),它应该提供数据属性的过滤版本 (measurementsByAttribute)。但是,计算属性的更改也发生在数据属性上。

过滤器使用 selectedAttributes 中的数据(前端下拉列表中选择的选项),然后在 measurementsByAttribute。此过滤器工作正常。问题是当我运行从 selectedAttributes 中清除数据的 clearAttribute 方法时(这部分工作成功) measurementsByAttribute 属性是过滤后的版本,所以我无法取回旧数据。

简而言之,我想保留 measurementsByAttribute 属性的原始版本,以便清除 selectedAttributes 属性,并为 filteredMeasurementsByAttribute 重置下拉表单提供所有原始数据。

我尝试将数据保存在常规 javascript 变量 measurementsByAttributeMaster 中,然后设置 measurementsByAttribute 给主人。不知何故,它们最终都只有过滤后的值。

我尝试更改循环数据的方式(例如,使用 forEach 而不是过滤器或映射)只是为了查看它是否会导致它编辑原始数据。没运气。我在注释掉的代码中保留了使用过滤器和映射的“原始”版本,因此您可以看到这两个版本。

非常感谢任何见解或帮助。

/**
 * 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();
        },        
    }
});

【问题讨论】:

    标签: javascript vue.js


    【解决方案1】:

    在 JavaScript 中,将变量设置为对象只会将 引用 复制到对象。在data() 中,您已将this.measurementsByAttribute 初始化为名为@9​​87654324@ 的局部变量,因此对this.measurementsByAttribute 的任何更改都会影响原始变量。

    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;
            }
        });
    
        //...
      }
    }
    

    一种解决方案是:

    1. Array.prototype.filter() 应用于this.measurementsByAttribute 以获得与过滤器选择匹配的测量值。
    2. 并使用 Array.prototype.map()the spread operator to shallow-clone 修改了 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])
                  }))
      }
    }
    

    【讨论】:

    • 你就是我需要的英雄!这工作得很好,让我终于明白了在 Javascript 和其他语言(如 PHP)中创建变量之间的区别。我没有意识到JS只是做了一个参考。再次感谢您。
    【解决方案2】:

    当计算的 prop 发生变化时,您正在启动对原始属性的更改。

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

    我只想让计算道具应用过滤器并返回结果,但没有任何 set 在计算上。

    如果您想要原始属性但在组件中到处都有反应性属性,则使用不同的变量可能会有所帮助。

    const measurementsByAttributeClone = {...measurementsByAttribute};
    
    // any changes to `measurementsByAttribute` will not impact `measurementsByAttributeClone`
    

    【讨论】:

    • 我只有设置器,因为我试图将它“重置”回原始值。即使我摆脱了二传手,它也在这样做。如果您查看实际使用 setter 的 clearAttribute 方法,您会发现我实际上已经在尝试使用克隆变量,在本例中称为measurementsByAttributeMaster。我什至在这个 js 文件之外有那个设置
    猜你喜欢
    • 2020-09-09
    • 2020-08-21
    • 2019-09-10
    • 2019-07-14
    • 2018-12-11
    • 1970-01-01
    • 2017-08-23
    • 2018-07-29
    • 2020-02-05
    相关资源
    最近更新 更多