【Vue.js】パフォーマンス改善のためのv-once・v-memoの使い方

【Vue.js】パフォーマンス改善のためのv-once・v-memoの使い方 Vue.js

Vue.jsでは、データが変わると画面が再描画(再レンダリング)されます。ふつうは気にしなくても十分に速いのですが、数百〜数千行のリストや、変わらないとわかっている大きなブロックでは、再描画を止めると効きます。この記事では、Vue 3の v-once と v-memo の使い方を、更新が止まる挙動や再描画回数の確かめ方まで含めて解説します。

先に結論です。v-onceは「最初の1回だけ描画して、以後は更新しない」ディレクティブです。v-memoは「指定した値が変わるまで、その部分の更新をスキップする」ディレクティブで、本命の使いどころはv-forの大きなリストです。どちらも、遅さを確認してから使う最適化です。

スポンサーリンク

v-onceの使い方:一度だけ描画して更新を止める

v-onceを付けた要素やコンポーネントは、最初に描画したあと、データが変わっても更新されません。子要素も含めて、まるごと固定されます。次の例で、通常の行とv-onceの行を比べてみます。

Vue: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を付けると、その時点の値で固定されます。これを知らずに付けると、「画面が更新されない」というバグになります。

効果が出るのは、中身が変わらない大きな塊です。ヘッダーやフッター、固定の説明文など、データに依存しない部分に付けます。

Vue:変わらないブロックに付ける
<template>
  <!-- 中身が変わらない大きな静的ブロック -->
  <footer v-once>
    <AppFooterLinks />
    <p>&copy; 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行(選択が外れた行と選ばれた行)しか再描画されません。

Vue:選択状態が変わった行だけ更新する
<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で一番多い失敗は、依存配列に入れ忘れることです。配列に入れていない値は、変わっても画面に反映されません。

Vue:依存配列の入れ忘れ
<!-- 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と同じ動きになります。

Vue:空配列のv-memo
<!-- 依存配列が空 = v-onceと同じ -->
<div v-memo="[]">
  {{ message }}
</div>

v-forでは同じ要素に書く

v-forの中でv-memoを使うときは、同じ要素に書く必要があります。外側の要素に付けても、各行には効きません。

Vue: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>

再描画が本当に減ったか確かめる

最適化は、効いたかどうかを確認してから採用します。簡単な方法は、テンプレートの中で関数を呼び、再描画のたびにログを出すことです。

Vue:再描画された行を数える
<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で配列やオブジェクトをループして表示する方法もあわせてご覧ください。

関連記事

よくある質問(FAQ)

Qv-onceを付けたのに、値が更新されません。
A仕様どおりの動きです。v-onceは最初の1回だけ描画し、以後は更新しません。値の変化を反映したいなら、v-onceを外すか、依存する値を指定できるv-memoを使ってください。
Qv-memoを付けたのに、一部の表示だけ古いままです。
Aテンプレートで使っている値が、依存配列に入っていない可能性があります。変わる可能性のある値は、すべて配列に入れてください。
Qv-memoはどのくらい速くなりますか?
A画面の構造しだいで、一概には言えません。数千行のリストでは差が出やすく、数十行なら体感は変わりません。開発者ツールで、実際の画面を計測して判断してください。
Qv-onceとv-ifは併用できますか?
A併用はできますが、v-onceの要素は最初の状態で固定されます。表示の切り替えが必要な要素に付けると、切り替わらなくなるので注意してください。

まとめ

v-onceは、変わらない大きな塊を一度だけ描画するために使います。v-memoは、v-forの大きなリストで、変わった行だけを更新するために使います。どちらも、画面が更新されなくなるリスクがあるため、依存する値の入れ忘れに気をつけ、再描画の回数を確かめてから採用してください。