【Vue3】v-memoディレクティブでレンダリングを最適化する
Vue3.2からv-memo
ディレクティブが追加されました。
v-memo
ディレクティブを利用することで、不要な再レンダリングを省略することができるので、パフォーマンスの向上につながります。
概要
v-memo
ディレクティブは、指定したすべての値が最後のレンダリング結果と同じであれば、サブツリー全体の再レンダリングをスキップします。
v-memoディレクティブの書き方
以下のように、コンポーネントが再レンダリングされた際に、配列に指定したvalueA
及びvalueB
が、最後のレンダリング時と同じ値の場合は、div
以下の要素の再レンダリングがスキップされます。
<div v-memo="[valueA, valueB]">
値に変更がなければ、この中身の再レンダリングがスキップされる
</div>
サンプルコード
ChildA.vueとChildB.vueを作成し、それぞれonUpdated()
内にログを出力することで、再レンダリングされたことを確認できるようにします。
また、後ほど再レンダリングをするためにprops
でmsg
を受け取ります。
<template>
<div class="child-a">ChildA: {{ msg }}</div>
</template>
<script>
import { onUpdated } from "vue";
export default {
name: "ChildA",
props: {
msg: {
type: String,
required: true,
},
},
setup() {
onUpdated(() => {
console.log("ChildA.vueが再レンダリングされました");
});
},
};
</script>
<template>
<div class="child-b">ChildB: {{ msg }}</div>
</template>
<script>
import { onUpdated } from "vue";
export default {
name: "ChildB",
props: {
msg: {
type: String,
required: true,
},
},
setup() {
onUpdated(() => {
console.log("ChildB.vueが再レンダリングされました");
});
},
};
</script>
そして、App.vueで上記2つのコンポーネントをインポートし、ChildA.vueは通常通りprops
にmsg
を渡します。
また、ChildB.vueはprops
にmsg
を渡しつつ、v-memo
でdummy
変数を指定します。
<template>
<child-a :msg="msg" />
<child-b v-memo="[dummy]" :msg="msg" />
<p>再レンダリング用: {{ dummy }}</p>
<button @click="clearMsg">メッセージ初期化</button>
<button @click="countUp">カウントアップ</button>
</template>
<script>
import { ref } from "vue";
import ChildA from "@/components/ChildA.vue";
import ChildB from "@/components/ChildB.vue";
export default {
name: "App",
components: {
ChildA,
ChildB,
},
setup() {
const msg = ref("メッセージ");
const clearMsg = () => {
msg.value = "";
};
// 再レンダリング用
const dummy = ref(0);
const countUp = () => {
dummy.value++;
};
return {
msg,
clearMsg,
dummy,
countUp,
};
},
};
</script>
こうすることで、通常であればmsg
が更新されると、props
でmsg
を受け取っている2つのコンポーネントは再レンダリングされますが、ChildB.vueはv-memo
の指定により、dummy
が変更された場合のみ再レンダリングされるため、「メッセージを初期化」ボタンをクリックしても再レンダリングされません。
逆に、「カウントアップ」ボタンをクリックすると、ChildB.vueが再レンダリングされます。
また、v-memo
の値に指定する配列を空にすると、v-once
と同等の機能になります。
<div v-memo="[]">
v-once(初回レンダリング以降は再レンダリングされない)と同じ
</div>
サンプルコード
以下コードは、カウントアップボタンを押してもv-memo="[]"
の方は画面が更新されません。
<template>
<p>通常: {{ value }}</p>
<p v-memo="[]">v-memo="[]": {{ value }}</p>
<button @click="incVal('value')">カウントアップ</button>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
const value = ref(0);
const incVal = () => {
value.value++;
};
return {
value,
incVal,
};
},
};
</script>
v-forとの併用
v-memo
の最も一般的なケースであるv-for
とv-memo
の併用方法です。
10個のリストを表示し、ボタンをクリックすると選択中のリストが変更されるサンプルコードです。
<template>
<button @click="reselectItemId">選択中のIDを変更</button>
<ul
v-for="itemId in itemIdList"
:key="itemId"
v-memo="[itemId === selectedItemId]"
>
<li>
ID: {{ itemId }}, selected:
{{ itemId === selectedItemId ? "← 選択中" : "" }}
</li>
</ul>
</template>
<script>
import { reactive, ref } from "vue";
export default {
name: "App",
setup() {
const maxItem = 10;
const itemIdList = reactive([]);
for (let i = 0; i < maxItem; i++) {
itemIdList.push(i);
}
const selectedItemId = ref(0);
const reselectItemId = () => {
const rand = Math.floor(Math.random(maxItem) * 10);
selectedItemId.value = rand;
};
return {
itemIdList,
selectedItemId,
reselectItemId,
};
},
};
</script>
上記コードは、「選択中のIDを変更」ボタンをクリックすると、先程選択中だったリストと、新たに選択されたリストの2つのみが更新され、残りの要素は更新されません。
今回は例のため10個のリストですが、これが1000個や10,000個のリストなど、数が大きくなった場合はパフォーマンスの向上が見込めます。
参考
Discussion