简体   繁体   中英

Strange behavior when removing an object from an array of objects in Vue

I have a Vue component and a root Vue instance. The instance contains an array of objects (for products) and the component is displayed in my HTML using a v-for loop for each product. This is what products.js looks like:

/**
 * Component to show products
 */
Vue.component('product', {
    props: ['product'],
    data: function() {
        return {
            localProduct: this.product
        };
    },
    template: `<div class="products">
                    <span>{{ localProduct.product }}</span>
                    <a href="javascript:void" v-on:click="remove">Remove</a>
                </div>`,
    methods: {
        remove: function() {

            var removeIndex = productsList.products.map(function(i) { return i.id; }).indexOf(this.localProduct.id);
            productsList.products.splice(removeIndex, 1);
        }
    }
});

/**
 * Instantiate root Vue instance
 */
var productsList = new Vue({
    el: '#products',
    data: {
        products: [{ id: 1, product: 'iPad' }, { id: 2, product: 'iPhone' }, { id: '3', product: 'AirPods' }]
    }
});

Now, the loop renders 3 DIVs for iPad, iPhone and AirPods. What's strange is, when I click the remove button for iPhone (productsList.products[1]), the HTML displays iPad and iPhone instead of iPad and AirPods (since we removed iPhone). I just can't figure out what's going on.

My array splice code seems to be working correctly as well. I console.logged the updated array after the splice function and it only included iPad and AirPods (correct) but strangely, the view is different! Can someone tell me what I'm doing wrong here? Thanks.

You should use the :key to keep track of the elements.

<product v-for="product in products"
         :product="product"
         :key="product.id"
         v-on:remove="removeProduct"></product>

I put together an example here.

 /** * Component to show products */ Vue.component('product', { props: ['product'], data: function() { return { localProduct: this.product }; }, template: `<div class="products"> <span>{{ localProduct.product }}</span> <a href="javascript:void" v-on:click="remove">Remove</a> </div>`, methods: { remove: function() { this.$emit('remove', this.localProduct.id); } } }); /** * Instantiate root Vue instance */ var productsList = new Vue({ el: '#products', data: { products: [{ id: 1, product: 'iPad' }, { id: 2, product: 'iPhone' }, { id: '3', product: 'AirPods' }] }, methods: { removeProduct: function(id) { this.products = this.products.filter(function(p) { return p.id !== id; }); } } });
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.min.js"></script> <div id="products"> <product v-for="product in products" :product="product" :key="product.id" v-on:remove="removeProduct"></product> </div>

I also did a bit of cleanup to your code like using filter() instead of splice() . And having the child component emit an event that the parent acts upon instead of the child directly changing the data on the parent.

To learn more about list rendering check out the docs .

If you change localProduct from a data property to a computed one, you can keep the rest of your code identical and it seems to work. Just pop this guy in place of data, in between props and template.

computed: {
    localProduct: function() {
        return this.product
    }
},

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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