配列内の要素削除が子コンポーネントに反映されない

勉強中の身です。
非常に初歩的な質問だとは思いますが、試行錯誤しても解決しないため質問させてください。

環境はLaravel + Vue 2.5.17 です。

課題として、商品一覧を行う ParentComponent と ChildComponent を作成しました。
ParentComponent : 商品一覧の制御を行う
ChildComponent : 商品情報の表示を行う

ParentComponentに商品追加ボタンを付けたり、
ChildComponentの削除ボタンからParentComponentの削除処理を行わせたりしておりました。
初めはChildComponentのv-bindにpropsのproductの値を割り当てていたのですが、
dataのtargetProductを割り当てるようにしたところ、
削除ボタンを押すと画面上の末尾のオブジェクトが削除されるようになってしまいました。

商品の削除は ParentComponent#deleteProduct(index) にて処理を行っています。
ChildComponentのdataに反映されるよう、splice()で配列要素を削除したのですが、
なぜ末尾のオブジェクトが削除されてしまうのか分かりません。

初歩的な質問だとは思いますが、教えていただけますでしょうか。

index.blade.php途中略

    <div id="app">
        <parent-component></parent-component>
    </div>

app.js

window.Vue = require('vue');

Vue.component('parent-component', require('../vue/components/ParentComponent.vue').default);
Vue.component('child-component', require('../vue/components/ChildComponent.vue').default);

const app = new Vue({
    el: '#app',
});

ParentComponent.vue

<template>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-3">計 {{products.length}} 件</div>
            <div class="col-md-9">
                <button class="btn btn-primary" @click="addProduct">追加</button>
            </div>
        </div>
        <div class="row">
            <div class="col-md-3" v-for="(product, index) in products">
                <child-component v-bind:product="product" v-bind:index="index" @deleteProduct="deleteProduct(index)" @editProduct="editProduct(index)"></child-component>
            </div>
        </div>
        <div class="row">
            <div class="col-12">
                <ul>
                    <li v-for="product in products">
                        {{ product.title }}
                    </li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                products: [
                    {
                        title: 'productAAA',
                        image: 'productsample.jpg',
                        description: 'AAAAAAA',
                    },
                    {
                        title: 'productBBB',
                        image: 'productsample.jpg',
                        description: 'BBBBBBB',
                    },
                    {
                        title: 'productCCC',
                        image: 'productsample.jpg',
                        description: 'CCCCCCC',
                    },
                    {
                        title: 'productDDD',
                        image: 'productsample.jpg',
                        description: 'DDDDDDD',
                    },
                    {
                        title: 'productEEE',
                        image: 'productsample.jpg',
                        description: 'EEEEEEE',
                    },
                    {
                        title: 'productFFF',
                        image: 'productsample.jpg',
                        description: 'FFFFFFF',
                    },
                ]
            }
        },
        mounted() {
            console.log('Parent Component mounted.')
        },
        methods: {
            addProduct(event) {
                this.products.push(
                    {
                        title: Math.random().toString(36).slice(-8),
                        image: 'productsample.jpg',
                        description: Math.random().toString(36).slice(-8),
                    }
                );
            },
            deleteProduct(index) {
                this.products.splice(index, 1);
            },
            editProduct(index) {
                console.log(this.products[index].title);
            },
        }
    }
</script>

ChildComponent.vue

<template>
    <div class="card">
        <div class="card-header">
            <div class="row">
                <div class="col-6" v-show="editMode">
                    <input type="text" v-model:value="targetProduct.title" class="form-control-sm" />
                    <button class="btn btn-sm btn-primary" v-show="editMode" @click="editDetect">OK</button>
                </div>
                <div class="col-6" v-show="!editMode">
                    <span v-show="!editMode">{{targetProduct.title}}</span>
                </div>
                <div class="col-6 text-right" v-show="!editMode">
                    <div class="btn-group">
                        <button @click="editProduct" class="btn-sm btn-warning">編集</button>
                        <button @click="$emit('deleteProduct')" class="btn-sm btn-danger">削除</button>
                    </div>
                </div>
            </div>
        </div>

        <img class="card-img-top" v-bind:src="'/images/products/'+targetProduct.image" v-bind:alt="targetProduct.title">

        <div class="card-body">
            {{ targetProduct.description }}
        </div>
    </div>
</template>

<script>
    export default {
        props: ['product'],
        data() {
            return {
                targetProduct: this.product,
                editMode: false,
            }
        },
        mounted() {
            console.log('Child Component mounted.')
        },
        methods: {
            editProduct() {
                this.editMode = true;
            },
            editDetect() {
                this.editMode = false;
                this.$emit('editProduct');
            }
        },
    }
</script>

4番目(productEEE)を削除した際の、productFFFのVueの状態
(propsの方は正しい値が入っていますが、dataの値はproductEEEの値を指しています。)

追記ですが、ParentComponent内の

<child-component v-bind:product="product" v-bind:index="index" @deleteProduct="deleteProduct(index)" @editProduct="editProduct(index)"></child-component>

に key を設定したら、問題無く指定した要素の削除が行われるようになりました・・・。

<child-component v-bind:product="product" v-bind:index="index" v-bind:key="product.title" @deleteProduct="deleteProduct(index)" @editProduct="editProduct(index)"></child-component>

なぜ、修正前のコードで動かないのかは理解できておりません・・・。

以下を読んでみると良いかもしれません。

Style Guide / Keyed v-for

また、

https://codesandbox.io

などでサンプルを載せて、触れるようにしてもらえると状況を理解しやすいでしょう。

アドバイス、ありがとうございます。

v-forを使用する場合は、基本的に :key を設定することで
Vueが指定した要素を把握できるようになるということですね。

とても参考になりました。ただ、釈然としない点があります。
今回は商品ということでIDを設定してキーとするのが本来だとは思いますが、
IDが設定できないようなオブジェクトに対して、
配列管理されているようなパターンもあると思いますが、
(例えば未登録の商品を一括登録するような画面など)
v-forは基本的にIDが設定されていることを前提としているのでしょうか。

:key=“index” 等としてみましたが、やはり上手く動きませんでした。

https://codesandbox.io

などでサンプルを載せて、触れるようにしてもらえると状況を理解しやすいでしょう。

こちらもアドバイスありがとうございます。
使用するのは初めてですが、載せてみました。

https://codesandbox.io/s/3qk3778xjp

ほぼ自己解決いたしました。
基本的にkeyに変更されるプリミティブ値を指定することはNGのようですね。
番号を自分で振るか、ハッシュ値などを検討することにします。

Qiita : Vue.js: v-forで項目インデックスをkey属性にしていいのか