🗝️

Vue.jsでkey属性を活用するためのキー割り振りルール

2022/10/20に公開約2,800字

Vue.js のテクニックとして、key 属性の値を変更すると強制的に再描画してくれるというものがあります。これは、リアクティビティが保たれているにもかかわらず、何らかの理由で即座に再描画がされないケースへの対処に有効です。

私の遭遇した問題でいうと、データオブジェクト(data)を元に動的に生成した CSS スタイルを適用した要素が、data を変化させても即座に再描画されないというものでした。この時は、key 属性に data を指定して解決しました。

sample1.vue
<!-- ※イメージ -->
<div :style="{fontSize: size+'px'}" :key="size"></div>

参考:https://michaelnthiessen.com/force-re-render/

しかし、key 属性といえば v-for ディレクティブで一意に割り振る必要があるものです。<transition> の子要素でも同様です。上の再描画テクニックを活用するために、これらと関係ない要素に対して key 属性を適当に割り振っても問題ないのでしょうか?

sample2.vue
<!-- size の変更のたびに再描画したい要素が複数あるとき -->
<div :key="size"></div>
<div v-for="(item, idx) in items" :key="idx">
  <div :key="size"></div>
</div>

この問題は、API リファレンスで述べられているキーの制約から考えることができます。

同じ共通の親を持つ子は、一意なキーを持っていなければなりません。

これは言い換えると、親が同じでない限りは、例えば複数の要素に同じ data をキーに指定しても問題ないということです。試しに、同じ親の子要素2つに同じ data をキーに指定すると警告が出ます。同じ警告を v-for で見たことがあるかもしれませんが、これは key 属性の一般的な特性によるものです。

duplicate_keys.vue
<!-- size はデータオブジェクト -->
<div>
  <div :key="size"></div>
  <div :key="size"></div>
</div>
<!-- [Vue warn]: Duplicate keys detected: '***'. This may cause an update error. -->

もしこれらの子要素を data の変化に反応させたいのであれば、識別子と組み合わせてキーを指定することでキーの重複を回避する方法があります。key 属性は String を許容するので、これは問題ありません。

unique_keys.vue
<div>
  <div :key="['1st', size].join('-')"></div>
  <div :key="['2nd', size].join('-')"></div>
</div>
<!-- no warning -->

v-for に惑わされないために

v-for_example_before.vue
<div v-for="(item, idx) in items" :key="idx">
  <div :key="[idx, size].join('-')"></div>
</div>

これはどうでしょうか。親要素の key 属性はひとまず無視してください。子要素の key 属性のキー指定は適切でしょうか。この場合、子要素の <div>v-for で作られる独立した親要素の中にあるので、一意性の制約に違反しません。書き下すとこうなります。

v-for_example_before.vue
<!-- size: 312 とする -->
<div>
  <div key="0-312"></div>
</div>
<div>
  <div key="1-312"></div>
</div>
<div>
  <div key="2-312"></div>
</div>
<div>
  <div key="3-312"></div>
</div>

ということは、:key="size" で問題ないのです。子要素同士で同じデータオブジェクトをキーに使用したい時だけ、気をつければ良いのです。v-for で気をつけるのは、それ自身のキー属性がユニークであるかどうかであり、子要素のキー指定とは別問題です。v-for に惑わされないように気をつけましょう。

v-for_example_after.vue
<div v-for="(item, idx) in items" :key="idx">
  <div :key="['1st', size].join('-')"></div>
  <div :key="['2nd', size].join('-')"></div>
</div>

冗長ですが、イメージを掴んでもらうために書き下しておきます。

v-for_example_after.vue
<!-- size: 312 とする -->
<div>
  <div key="1st-312"></div>
  <div key="2nd-312"></div>
</div>
<div>
  <div key="1st-312"></div>
  <div key="2nd-312"></div>
</div>
<div>
  <div key="1st-312"></div>
  <div key="2nd-312"></div>
</div>
<div>
  <div key="1st-312"></div>
  <div key="2nd-312"></div>
</div>

まとめ

key属性による再描画テクニックを活用する際は、次のことを知っておきましょう。

  • Vueアプリ内の全てのキーが常にグローバルに一意である必要はない。
  • v-for<transition> を使用するケース、共通の親を持つ子要素にキーを指定するケースでは一意にする。
  • 共通の親を持つ子要素同士で共通のデータオブジェクトの変更を元に再描画させたい場合は、データオブジェクトにそれぞれの識別子組み合わせた文字列をキーに指定するコツがある。

Discussion

ログインするとコメントできます