結論
v-forしている要素にkey属性をバインドしましょう。
key属性として指定する値は重複しなければ何でもいいです。
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
想定事象
- li要素をv-forで展開している配列の要素の一部または全部を代入で置き換えたのに、画面上では反映されない。
- 検索結果のリスティングで、最終ページ→その他のページに画面を切り替えたとき、要素の数は変わっているのに中身が書き換わっていない。
- Vue DevToolsで確認すると、配列の値それ自体は期待通り更新されている。
原因と仕組み
Vueは v-for で描画される要素のリストについて、割り当てられたDOM要素の中身を書き換えるという戦略を取ります。
Vue が v-for で描画された要素のリストを更新する際、標準では “その場でパッチを適用する” (in-place patch) 戦略が用いられます。データのアイテムの順序が変更された場合、アイテムの順序に合わせて DOM 要素を移動する代わりに、 Vue は各要素にその場でパッチを適用して、その特定のインデックスに何を描画するべきかを確実に反映します。これは Vue 1.x にあった機能の track-by="$index" に似たものです。
しかし配列の要素を書き換えたり入れ替えたりされると、Vueは配列の要素の中身まで変化を追跡できません。
この標準のモードは効率がいいです。しかしこれは、描画されたリストが子コンポーネントの状態や、一時的な DOM の状態に依存していないときにだけ適しています (例: フォームのインプットの値)。
こうした変化を追跡できるよう監視するためには、v-for している要素に key 属性を与えるのが一番手っ取り早いです。
Vue が各ノードの識別情報を追跡できるヒントを与えるために、また、先ほど説明したような既存の要素の再利用と並び替えができるように、一意な key 属性を全てのアイテムに与える必要があります:
ここのページでは「推奨される」と訳されていますが、スタイルガイドではより「使用してください」と強い表現で書かれています。
繰り返される DOM の内容が単純な場合や、性能向上のために標準の動作に意図的に頼る場合を除いて、可能なときはいつでも v-for に key 属性を与えることが推奨されます。
常に v-for に対しては key を使用してください。
ちなみにあなたは配列の更新を単純に代入によって行っていると思いますが、以下のメソッドを用いることでも変更を監視することができます。
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
Vue は画面の更新もトリガするために、監視された配列の変更メソッドを抱合 (wrap) します。抱合されたメソッドは次のとおりです:
余談
Reactなどで用いられているJSXでは map() メソッドなどで配列を展開しようとすると一意なkey属性を与えるようエラーが出るので、こうしたバグが未然に防がれているようです。