Vue 3で「Maximum recursive updates exceeded」エラー

Vue 3 を使って開発しているときに「Maximum recursive updates exceeded」というエラーに遭遇したことはありませんか?これは Vue が内部でレンダリングを繰り返しすぎて、無限ループ状態に陥ったことを検知したときに表示される典型的なエラーです。初心者から中級者までよくハマるポイントであり、特に watchcomputed の使い方を誤ると発生します。

本記事では、このエラーが発生する原因と、実際のデバッグ・解決方法を技術者向けに詳しく解説します。

1. エラーの発生条件と仕組み

Vue のリアクティブシステムは、状態の変更を検知して DOM を再描画します。しかし、状態変更の副作用でさらに同じ状態を更新してしまうと、再描画が無限に繰り返されます。これが「Maximum recursive updates exceeded」というエラーの正体です。

例えば、watch で監視している値を更新する際に再度同じ値を書き換えてしまうと、無限ループに入ります。

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newVal) => {
  // ❌ 悪い例: watch 内で同じ値を更新
  count.value = newVal + 1
})
</script>

このコードは count が変わるたびに watch が発火し、その中で再び count を更新するため再度発火…という無限ループが発生します。Vue は一定回数でこれを検知し、エラーを投げます。

2. よくある原因パターン

このエラーは以下のようなケースでよく発生します。

  • watch のコールバック内で同じ値を更新している
  • computed プロパティで setter が state をループ的に更新している
  • 親コンポーネントから渡された props を直接書き換えている
  • 無限再レンダリングを引き起こす条件分岐や非同期処理の誤用

特に computed の setter 内で別の reactive 値を更新するときは、依存関係が循環しないかを確認する必要があります。

3. デバッグ方法と再現確認

無限ループの原因を特定するには、まず どの変数が更新され続けているか を確認します。console.log を適切に挿入して値の変化を追跡すると、どこで再代入が行われているかが分かります。

また、Vue Devtools を使えばリアクティブな依存関係や再レンダリングのトリガーを可視化できるため、原因特定が容易になります。

例として、次のようにログを追加してデバッグします。

watch(count, (newVal) => {
  console.log('count changed:', newVal)
  // 必要であれば条件を付けて更新する
  if (newVal < 10) {
    count.value = newVal + 1
  }
})

条件分岐を追加することで、無限に増え続けるのを防ぎ、一定の回数で止めることができます。

4. 正しい修正方法

エラーを解決するには、状態更新の循環を断ち切る必要があります。以下のアプローチが有効です。

  1. watch 内で同じ変数を直接更新しない。必要なら別の state を更新する
  2. computed の setter 内で元の依存変数を更新しない
  3. props はそのまま使い、必要なら子コンポーネント内でコピーしてから更新する
  4. 無限ループを防ぐ条件分岐を設ける

修正版コード例:

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)
const safeCount = ref(0)

watch(count, (newVal) => {
  if (newVal < 10) {
    safeCount.value = newVal + 1
  }
})
</script>

これにより count の変更は safeCount に反映されますが、count 自体は再更新されないため無限ループが防げます。

5. 実運用での対策とベストプラクティス

実際の開発現場では、無限ループはバグの温床となりパフォーマンス劣化やクラッシュを招きます。以下のベストプラクティスを守ることで予防できます。

  • watch より computed や watchEffect の利用を優先する
  • データフローを一方向に保つ(Vuex / Pinia を使う場合も同様)
  • デバッグ時は Vue Devtools で再レンダリングの発生回数を観察する
  • 無限ループを防ぐためのガード条件(if 文や early return)を必ず入れる

特に大規模プロジェクトでは、循環依存が見落とされやすいため、レビュー時に watch / computed の実装を重点的にチェックすることが重要です。

6. まとめ

「Maximum recursive updates exceeded」エラーは、Vue のリアクティブ機構を理解していないと非常にハマりやすい問題です。しかし原因はシンプルで、多くの場合は 同じ値を繰り返し更新してしまっている ことが根本原因です。

状態更新の流れを整理し、watch や computed の依存関係を適切に設計することで解消できます。コンソールログや Vue Devtools を活用し、再現条件を明確にした上で修正するのが最短ルートです。

参考

タイトルとURLをコピーしました