Vue 3.2で追加されたv-memoについて解説してみる

2021年8月5日にVue 3.2がリリースされました。CompositionAPIのシンタックスシュガーが安定版になったり色々と嬉しい内容でした。

そんな中、v-memoというディレクティブが実装されています。とても地味な機能で活躍するシーンは少ないものの、特定のシーンにおいてはパフォーマンスの改善に役立つ重要な機能となるので、今回はv-memo ディレクティブをわかりやすく解説してみたいと思います。

## v-memoってなに
リリースノートによると「テンプレートツリーの一部をメモ化する機能」と説明されています。メモ化と言われてもよくわかりませんね。

簡単に言うと**v-memoディレクティブが付与されたノード内のDOMは更新されないなくなる**、という挙動になります。つまりv-onceディレクティブを付与した場合と同じなのですが、v-memoはvalueにbooleanの配列を設定することができます。
v-memoは設定された配列内の値がすべてtrueになった場合にv-onceと同様の効果を発揮します。
## デモ
わかりやすく説明するためにデモを作ってみました。
10件の要素を画面に表示して、フォームによって各要素のnameを編集できるようにします。編集内容を適用する際に**v-memoによってDOMを更新する領域を限定**しています。

```vue
<script setup>
import { ref } from 'vue';

const list = ref([
  { id: 1, name: 'LABEL 1' },
  { id: 2, name: 'LABEL 2' },
  { id: 3, name: 'LABEL 3' },
  { id: 4, name: 'LABEL 4' },
  { id: 5, name: 'LABEL 5' },
  { id: 6, name: 'LABEL 6' },
  { id: 7, name: 'LABEL 7' },
  { id: 8, name: 'LABEL 8' },
  { id: 9, name: 'LABEL 9' },
  { id: 10, name: 'LABEL 10' },
]);
const selected = ref(0);
const text = ref('');

function clickHandler() {
  list.value.find((x) => x.id === selected.value).name = text.value;
  // v-memoの判定を発生させてDOMを更新する
  selected.value = 0;

  text.value = '';
}
</script>

<template>
  <select v-model="selected">
    <option></option>
    <template v-for="item in list" :key="item.id">
      <option :value="item.id">{{ item.id }}</option>
    </template>
  </select>
  <input type="text" v-model="text" />
  <button @click="clickHandler">Apply</button>
  <div class="wrapper">
    <div
      class="badge"
      v-for="item in list"
      :key="item.id"
      v-memo="[item.id !== selected]"
    >
      <div>
        {{ item.name }}
      </div>
    </div>
  </div>
</template>
<style scoped>
button,
input[type='text'] {
  margin-left: 0.5rem;
}
.wrapper {
  display: flex;
  flex-wrap: wrap;
  gap: 30px 20px;
  margin-top: 2rem;
  width: 700px;
}
.badge {
  background-color: #eee;
  padding: 0.2rem 1rem;
  border-radius: 5px;
}
</style>
```
上記のソースを実行するとブラウザには以下のような画面が表示されます。この状態では各要素のv-memo値はtrueとなるので、全てv-once状態となり以降のレンダリングでDOMの更新はされません。
![スクリーンショット_2021-10-12_16.28.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/58f72f2d-2b44-83a1-431a-b10381232a68.png)
2番目のラベルを編集してみます。
![スクリーンショット_2021-10-12_16.28.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/77b719b1-8922-536e-57dc-d0f38525abf0.png)
Applyボタンをクリックすると2番目のラベルが更新されました。（DOMが更新された）
![スクリーンショット_2021-10-12_16.28.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/3bad4d7e-c051-3a30-eec0-67464b0f3b58.png)
この時、**ラベル2以外のDOMはv-memoがtrueなので更新されていない**ことになります。さらに次は8番目のラベルを編集してみます。
![スクリーンショット_2021-10-12_16.29.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/6aaabf8b-5055-641f-2583-1af7e5a058c4.png)
Applyボタンをクリックすると8番目のラベルが更新されました。（DOMが更新された）
![スクリーンショット_2021-10-12_16.29.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2221418/ea7454e9-2f21-2ae7-b8cb-b16750ef4050.png)
以上の通り、v-memoディレクティブは**設定された配列が真の場合にのみ発動し、またその真偽が変更されたタイミングでDOMを更新する**、という特性があります。
## まとめ
デモの通り、v-memoは大量にリストされた子要素の中で必要なDOMのみ更新させることで、レンダリングにかかるコストを抑えてパフォーマンスを最適化します。
デモでは10件のリストでしたが、[公式リファレンス](https://v3.ja.vuejs.org/api/directives.html#v-memo)によると**1000件以上のリスト**で効果が発揮されるそうです。

1000件以上のリストを扱うケースはそう多くないかもしれませんが、パフォーマンスの低下に悩んだ際は**v-memoディレクティブ**の採用を検討してみてはいかがでしょうか。

以上