Vue.jsでは、データが変わると画面が再描画(再レンダリング)されます。ふつうは気にしなくても十分に速いのですが、数百〜数千行のリストや、変わらないとわかっている大きなブロックでは、再描画を止めると効きます。この記事では、Vue 3の v-once と v-memo の使い方を、更新が止まる挙動や再描画回数の確かめ方まで含めて解説します。
先に結論です。v-onceは「最初の1回だけ描画して、以後は更新しない」ディレクティブです。v-memoは「指定した値が変わるまで、その部分の更新をスキップする」ディレクティブで、本命の使いどころはv-forの大きなリストです。どちらも、遅さを確認してから使う最適化です。
v-onceの使い方:一度だけ描画して更新を止める
v-onceを付けた要素やコンポーネントは、最初に描画したあと、データが変わっても更新されません。子要素も含めて、まるごと固定されます。次の例で、通常の行とv-onceの行を比べてみます。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">増やす</button>
<p>通常:{{ count }}</p>
<p v-once>v-once:{{ count }}</p>
</template>
ボタンを押すと、「通常」の行は増えていきますが、「v-once」の行は0のままです。つまり、リアクティブな値にv-onceを付けると、その時点の値で固定されます。これを知らずに付けると、「画面が更新されない」というバグになります。
効果が出るのは、中身が変わらない大きな塊です。ヘッダーやフッター、固定の説明文など、データに依存しない部分に付けます。
<template>
<!-- 中身が変わらない大きな静的ブロック -->
<footer v-once>
<AppFooterLinks />
<p>© 2026 Example Inc.</p>
</footer>
</template>
見出し1行のような小さな静的要素に付けても、効果はほとんど出ません。Vueは静的なノードをコンパイル時に最適化しているためです。v-onceは、子コンポーネントを含む大きな塊に付けたときに意味があります。
v-memoの使い方:値が変わるまで更新をスキップする
v-memoは、Vue 3.2で追加されたディレクティブです。配列で渡した値が、前回と同じなら、その部分の更新を丸ごとスキップします。配列の値は1つずつ比較され、1つでも変わると再描画されます。
v-forの大きなリストで使う(本命の使い方)
代表的な使い道は、選択状態のあるリストです。何も工夫しないと、1行を選ぶたびに全行が再描画の対象になります。v-memoに「この行が選択されているか」だけを渡せば、状態が変わった2行(選択が外れた行と選ばれた行)しか再描画されません。
<script setup>
import { ref } from 'vue'
const selected = ref(null)
const items = ref(
Array.from({ length: 1000 }, (_, i) => ({ id: i, label: `項目 ${i}` }))
)
</script>
<template>
<ul>
<li
v-for="item in items"
:key="item.id"
v-memo="[item.id === selected]"
:class="{ active: item.id === selected }"
@click="selected = item.id"
>
{{ item.label }}
</li>
</ul>
</template>
この例では、v-memo="[item.id === selected]"が、各行の「選択されているか」をtrueかfalseで返します。この値が変わらない行は、更新がスキップされます。
依存配列には、表示に使う値をすべて入れる
v-memoで一番多い失敗は、依存配列に入れ忘れることです。配列に入れていない値は、変わっても画面に反映されません。
<!-- NG: nameが変わっても画面が更新されない -->
<div v-memo="[user.id]">
<p>ID: {{ user.id }}</p>
<p>名前: {{ user.name }}</p>
</div>
<!-- OK: 表示に使う値をすべて依存配列に入れる -->
<div v-memo="[user.id, user.name]">
<p>ID: {{ user.id }}</p>
<p>名前: {{ user.name }}</p>
</div>
テンプレートで使う値のうち、「変わる可能性があるもの」は、すべて依存配列に入れます。入れ忘れは、エラーが出ずに画面だけが古いままになるので、見つけにくい不具合になります。
空の配列はv-onceと同じ
v-memo="[]"は、v-onceと同じ動きになります。
<!-- 依存配列が空 = v-onceと同じ -->
<div v-memo="[]">
{{ message }}
</div>
v-forでは同じ要素に書く
v-forの中でv-memoを使うときは、同じ要素に書く必要があります。外側の要素に付けても、各行には効きません。
<!-- NG: v-memoがv-forの外側にあり、各行に効かない -->
<div v-memo="[selected]">
<p v-for="item in items" :key="item.id">{{ item.label }}</p>
</div>
<!-- OK: v-forと同じ要素に書く -->
<p v-for="item in items" :key="item.id" v-memo="[item.id === selected]">
{{ item.label }}
</p>
再描画が本当に減ったか確かめる
最適化は、効いたかどうかを確認してから採用します。簡単な方法は、テンプレートの中で関数を呼び、再描画のたびにログを出すことです。
<script setup>
import { ref } from 'vue'
let renderCount = 0
// テンプレート内で呼ぶと、その行が再描画されるたびに実行される
function track(id) {
renderCount++
console.log(`再描画: ${id}(累計 ${renderCount} 回)`)
return ''
}
const selected = ref(null)
const items = ref(Array.from({ length: 5 }, (_, i) => ({ id: i, label: `項目 ${i}` })))
</script>
<template>
<ul>
<li
v-for="item in items"
:key="item.id"
v-memo="[item.id === selected]"
@click="selected = item.id"
>
{{ item.label }}{{ track(item.id) }}
</li>
</ul>
</template>
最初は5行すべてが描画されるので、ログが5回出ます。ここで1行をクリックすると、v-memoがあれば選択が変わった行の分だけログが出ます。v-memoを外して同じ操作をすると、全行分のログが出るので、違いがわかります。実際の画面では、ブラウザの開発者ツールのPerformanceタブや、Vue DevToolsのタイムラインでも確認できます。確認が終わったら、ログ用の関数は削除してください。
v-onceとv-memoの違い
v-onceは、一度描画したら二度と更新しません。v-memoは、指定した値が変わったときだけ更新します。そのため、変わらないとわかっているならv-once、特定の値が変わったときだけ更新したいならv-memoと使い分けます。v-memoはv-onceより柔軟ですが、依存配列を自分で管理する分、書き間違えたときのリスクも高くなります。なお、v-onceはVue 2にもありますが、v-memoはVue 3.2以降のみです。
使うべきでない場面
- 行数が少ないリスト:数十行程度なら、最適化しなくても体感は変わりません。コードが複雑になるだけです。
- 表示する値が頻繁に変わる部分:依存配列の値が毎回変わるなら、スキップされず、比較のコストだけが増えます。
- 遅さを確認していない段階:まず計測します。原因は描画ではなく、計算やAPIの待ち時間かもしれません。
描画の前の計算が重い場合は、computedプロパティでキャッシュするほうが効果的です。リストの絞り込みは検索フィルター付きリストの作成、リストの書き方の基本はv-forで配列やオブジェクトをループして表示する方法もあわせてご覧ください。
関連記事
- 初期表示そのものを軽くするならコードスプリッティングと遅延読み込み
- 画像の読み込みを遅らせるなら画像の遅延読み込み(Lazy Loading)
- 表示の出し分けはv-ifで条件分岐させる方法
よくある質問(FAQ)
v-onceは最初の1回だけ描画し、以後は更新しません。値の変化を反映したいなら、v-onceを外すか、依存する値を指定できるv-memoを使ってください。v-onceの要素は最初の状態で固定されます。表示の切り替えが必要な要素に付けると、切り替わらなくなるので注意してください。まとめ
v-onceは、変わらない大きな塊を一度だけ描画するために使います。v-memoは、v-forの大きなリストで、変わった行だけを更新するために使います。どちらも、画面が更新されなくなるリスクがあるため、依存する値の入れ忘れに気をつけ、再描画の回数を確かめてから採用してください。

