Vue.jsのライフサイクルを図解で理解!コンポーネントの一生を追う

はい、承知いたしました。Vue.jsのライフサイクルに関する、約5000語の詳細な解説記事を作成します。図解そのものはテキストで表現できませんが、図の内容や各段階のフローを言葉で非常に丁寧に説明することで、理解を助けるよう努めます。


Vue.jsのライフサイクルを図解で理解!コンポーネントの一生を追う

Vue.jsは、現代的なユーザーインターフェースを構築するためのプログレッシブなJavaScriptフレームワークです。その最大の魅力の一つは、宣言的なレンダリングとコンポーネント指向の開発モデルにあります。Vueコンポーネントは、アプリケーションを構成する自己完結型の部品であり、それぞれが独立した状態と振る舞いを持ちます。

しかし、このコンポーネントが画面に表示され、ユーザー操作に応答し、最終的に画面から消えるまでには、内部で様々なプロセスが進行しています。この一連のプロセスが「コンポーネントのライフサイクル」です。コンポーネントは、まさに生命体のように「誕生」し、活動し、「死」を迎えるのです。

このライフサイクルを理解することは、Vue.jsを用いた開発において非常に重要です。なぜなら、特定の処理を「いつ」「どこで」実行すべきかを正確に判断できるようになるからです。例えば、

  • コンポーネントが表示される前に初期データを取得したい
  • コンポーネントが完全にDOMに描画された後に、特定のDOM要素を操作したい
  • コンポーネントが画面から消える前に、不要になったイベントリスナーを解除したい

といった要求に対して、適切なライフサイクルフックを使うことで、クリーンで効率的、そしてバグの少ないコードを書くことができます。逆に、ライフサイクルを曖昧なままにしておくと、「なぜか処理がうまくいかない」「メモリリークが発生する」「パフォーマンスが悪い」といった問題に直面する可能性が高まります。

この記事では、Vue.jsコンポーネントの誕生から死までの「一生」を追いながら、その各段階で用意されている「ライフサイクルフック」について、図解をイメージしながら徹底的に掘り下げて解説します。それぞれのフックがいつ呼ばれるのか、その時点でのコンポーネントの状態はどうなっているのか、そしてどのようなユースケースがあるのかを、具体的な例を交えながら詳しく見ていきましょう。

この記事を読むことで、あなたはVue.jsコンポーネントの内部動作に対する深い洞察を得られ、より自信を持ってVue.jsアプリケーションを開発できるようになるでしょう。

ライフサイクルとは何か? Vueインスタンス/コンポーネントの一生

まず、「ライフサイクル」という言葉がVue.jsにおいて何を指すのかを明確にしましょう。

Vue.jsにおけるライフサイクルとは、Vueインスタンスまたはコンポーネントが作成されてから、DOMにマウントされ、データの変更によって更新され、最終的に破棄されるまでの一連のフロー(流れ)のことです。このフローの特定の節目(段階)で、開発者が独自のコードを実行できるように提供されているのが「ライフサイクルフック(Lifecycle Hooks)」です。

コンポーネントのライフサイクルは、大きく分けて以下の4つの主要な段階に分けられます。

  1. Creation(生成): インスタンスが初期化される段階。データが監視され、イベントシステムが設定されます。まだDOMにはマウントされていません。
  2. Mounting(マウント): インスタンスがテンプレートをコンパイルし、仮想DOMを生成し、それを実際のDOMにマウント(挿入)する段階です。これにより、コンポーネントが画面に表示されます。
  3. Updating(更新): コンポーネントのリアクティブなデータ(datapropscomputedなど)が変更されたときに発生する段階です。仮想DOMが再生成され、実際のDOMが更新されます。
  4. Destruction(破棄): インスタンスが破棄される段階です。DOMから削除され、関連付けられたイベントリスナーやディレクティブなどが解除されます。

これらの各段階の開始や完了のタイミングで、Vueは特定のライフサイクルフック関数を呼び出します。私たちは、これらのフックに関数を登録することで、コンポーネントの「一生」のさまざまな時点で必要な処理を実行できるのです。

Options APIを使用する場合、これらのフックはコンポーネントオプションとして定義します。例えば、createdというフックに関数を登録するには、コンポーネント定義オブジェクトにcreated: function() { ... }のように記述します。

ライフサイクル全体の俯瞰(図解イメージ)

(ここでは図を直接表示できませんが、以下のようなフローを想像してください。)

コンポーネントのライフサイクルは、多くの場合、以下のような流れで進行します。

mermaid
graph TD
A[Start] --> B{New Vue Instance};
B --> C[Init Events & Lifecycle];
C --> D[beforeCreate Hook];
D --> E[Init Injections & Reactivity];
E --> F[created Hook];
F --> G{Has el option?};
G -- Yes --> H{Has template option?};
G -- No --> L[Call \$mount()];
H -- Yes --> I[Compile template into render function];
H -- No --> J[Compile el's outerHTML into render function];
I --> K[beforeMount Hook];
J --> K;
K --> M[Create VDOM node and mount VDOM to real DOM];
M --> N[mounted Hook];
N --> O{Data Change?};
O -- Yes --> P[beforeUpdate Hook];
P --> Q[Re-render VDOM];
Q --> R[Patch VDOM to real DOM];
R --> S[updated Hook];
S --> O;
O -- No --> T{Call \$destroy() or v-if=false?};
T -- Yes --> U[beforeDestroy/beforeUnmount Hook];
U --> V[Teardown watchers, child components, event listeners];
V --> W[destroyed/unmounted Hook];
W --> X[End];
T -- No --> X; % component stays alive if not destroyed

上記のフローチャートは、Options APIにおける主要なライフサイクルフックと、その呼び出しのタイミング、およびVueの内部処理の概要を示しています。Vue 3ではフック名が一部変更されていますが、基本的な流れは同じです(beforeDestroy -> beforeUnmount, destroyed -> unmounted)。

この図を念頭に置きながら、各段階とそこに紐づくフックについて詳しく見ていきましょう。

Creation Phase(生成段階): コンポーネントの誕生

コンポーネントが初めてインスタンス化される段階です。まだDOM要素との関連付けは行われていません。

1. beforeCreate フック

  • 呼び出されるタイミング: インスタンスが初期化された直後、データ監視やイベントシステムがセットアップされる前に呼び出されます。
  • インスタンスの状態:
    • datacomputedmethodswatchなどのオプションはまだ処理されておらず、thisコンテキストでこれらのプロパティやメソッドにアクセスしようとしてもundefinedとなります。
    • DOM要素(this.$el)にもまだアクセスできません。
  • ユースケース: 非常に限られています。主にデバッグ目的で、インスタンスの初期状態を観測するために使用されることがあります。あるいは、インスタンスの初期化処理に関するロジックを、データやメソッドに依存しない形で実行したい場合に使う可能性もゼロではありませんが、一般的ではありません。ほとんどの初期化ロジックは次のcreatedフックで行うのが適切です。
  • 注意点: この段階ではインスタンスの主要な機能(リアクティブデータやメソッド)が利用できないため、できることは非常に少ないです。

(図解イメージにおける位置: Init Events & Lifecycle の直後)

javascript
// Options APIでの例
export default {
beforeCreate() {
console.log('beforeCreate: インスタンス初期化後、データ・メソッド未準備');
// ここで this.dataProp や this.myMethod() にアクセスしようとするとエラーまたは undefined
// console.log(this.message); // エラーまたは undefined
// console.log(this.fetchData); // エラーまたは undefined
},
data() {
return {
message: 'Hello Vue',
};
},
methods: {
fetchData() {
// ...
},
},
};

beforeCreateフックの実行が完了すると、Vueはインジェクションの解決や、datapropscomputedなどのオプションを処理し、リアクティブシステムを設定します。これにより、データの変更がビューに反映される準備が整います。

2. created フック

  • 呼び出されるタイミング: インスタンスが生成され、データ監視(リアクティビティの設定)とイベントシステムがセットアップされた後に呼び出されます。ただし、まだDOMにはマウントされていません
  • インスタンスの状態:
    • datacomputedmethodswatchなどのオプションが利用可能になり、thisコンテキストを通じてアクセスできます。
    • リアクティブシステムが動作しているため、dataプロパティの値を変更すると、将来的にビューの更新がトリガーされます。
    • しかし、this.$elはまだ存在しません(または、未コンパイルのテンプレート要素を参照している可能性があります)。テンプレートの内容はまだ実際のDOMに描画されていません。
  • ユースケース: このフックは、コンポーネントが表示される前に初期データをセットアップしたり、非同期処理(APIコールなど)を実行したりするのに最も適した場所です。
    • APIからのデータ取得: サーバーからデータをフェッチして、コンポーネントのdataプロパティにセットする典型的なシナリオです。DOMへのアクセスは不要なため、この段階で十分です。
      javascript
      created() {
      console.log('created: インスタンス生成&データ・メソッド準備完了、DOM未マウント');
      console.log(this.message); // 'Hello Vue' が表示される
      this.fetchInitialData(); // メソッド呼び出し可能
      },
      methods: {
      async fetchInitialData() {
      try {
      const response = await fetch('/api/data');
      const data = await response.json();
      this.items = data; // データプロパティを更新 (リアクティブ)
      } catch (error) {
      console.error('Failed to fetch data:', error);
      }
      }
      }
    • 初期設定値の読み込み: 例えば、localStorageからユーザー設定を読み込むなどの処理。
    • コンポーネント内部のイベントリスナー設定: DOMイベントではなく、Vueのカスタムイベントやグローバルイベントバスのリスナー設定など。(ただし、これらは後述するbeforeUnmountで必ず解除する必要があります)
  • 注意点: 繰り返しになりますが、この時点ではまだコンポーネントはDOMにマウントされていません。したがって、DOM要素のサイズを取得したり、DOM構造に依存する操作(特定の要素にフォーカスを当てるなど)を行ったりすることはできません。DOM操作が必要な場合は、mountedフックを使用する必要があります。

(図解イメージにおける位置: Init Injections & Reactivity の直後)

Creation段階のまとめとして、beforeCreateはほとんど使いませんが、createdはコンポーネントの初期化、特に非同期データフェッチの一般的な場所です。この段階で、コンポーネントは「内部的な準備」を終え、データやメソッドが利用可能になりますが、「外の世界」(DOM)との関わりはまだ始まっていません。

Mounting Phase(マウント段階): コンポーネントの表示

Creation段階でインスタンスの内部的な準備が整ったら、次はそれをユーザーに見える形でDOMに「マウント」する段階です。テンプレートがレンダリングされ、実際のDOM要素が生成・挿入されます。

3. beforeMount フック

  • 呼び出されるタイミング: テンプレートがコンパイルされ、レンダリング関数が生成された後、最初のレンダリングが行われ、DOMにマウントされる直前に呼び出されます。
  • インスタンスの状態:
    • datacomputedmethodsなどは引き続き利用可能です。
    • レンダリング関数は準備できていますが、まだ最初の仮想DOMツリーが生成された段階ではありません。
    • this.$elは、elオプションが指定されている場合はその要素を参照しますが、テンプレートの内容がマウントされる前の状態です。まだコンポーネントの内容が実際のDOMに反映されていません。
  • ユースケース: このフックは、createdフックと同様にあまり頻繁には使用されません。レンダリングが開始される直前の最後の調整を行うために使用される可能性はありますが、createdでデータ準備を終えるのが一般的です。
    • パフォーマンスが非常にクリティカルな状況で、レンダリング前に最後のリアクティブデータ変更を行う(ただし、非同期処理の結果を待つ場合はcreatedやその中のawaitで対応した方が自然です)。
    • サーバーサイドレンダリング(SSR)の場合、このフックはクライアント側でのみ実行されます。
  • 注意点: ここでもまだ、コンポーネントの実際のDOM要素にはアクセスできません。DOM操作はこの段階では意味をなしません。

(図解イメージにおける位置: Compile template/el の直後、Create VDOM... の直前)

javascript
// Options APIでの例
export default {
beforeMount() {
console.log('beforeMount: テンプレートコンパイル後、DOMマウント直前');
// this.$el にアクセスしても、コンポーネントのコンテンツはまだ描画されていない
// console.log(this.$el); // elオプションで指定された要素、または undefined
},
// ... created, data, methods など
};

beforeMountフックの実行後、Vueはインスタンスのレンダリング関数を実行し、最初の仮想DOMツリーを生成します。そして、その仮想DOMツリーを基に実際のDOM要素を作成し、elオプションで指定された要素内に挿入(マウント)します。

4. mounted フック

  • 呼び出されるタイミング: コンポーネントがDOMに完全にマウントされ、ユーザーに見える状態になった直後に呼び出されます。
  • インスタンスの状態:
    • コンポーネントのテンプレートの内容が、実際のDOMとしてthis.$elを通じてアクセスできるようになります。
    • 子コンポーネントも再帰的にマウントされた状態です(ただし、全ての子コンポーネントのmountedフックが完了しているわけではないことに注意が必要な場合もあります)。
  • ユースケース: このフックは、コンポーネントの実際のDOM要素にアクセスして操作する必要がある処理を行うのに最も適した場所です。
    • DOM操作: jQueryなどのライブラリを使ってDOM要素を操作する、Canvas要素に描画する、D3.jsなどでグラフを描画する、特定の要素にフォーカスを当てるなど。
      javascript
      mounted() {
      console.log('mounted: DOMに完全にマウントされました');
      // this.$el を通じてコンポーネントのルートDOM要素にアクセス可能
      const element = this.$el.querySelector('.my-element');
      if (element) {
      // 要素に対してDOM操作を行う
      element.classList.add('is-mounted');
      // サードパーティライブラリの初期化
      // 例えば、新しい要素をターゲットにしてライブラリを初期化
      // new MyDOMPlugin(element, { /* options */ });
      }
      // input要素にフォーカスを当てる例
      // this.$refs.myInput.focus();
      },
      // ...
      // template: '<input ref="myInput" />'
    • サードパーティライブラリの初期化: 特定のDOM要素を引数として取るようなJavaScriptライブラリ(例: カレンダーピッカー、カスタムスクロールバー、リッチテキストエディタなど)をコンポーネントに統合する際に、このフックで初期化を行います。
    • DOMイベントリスナーの登録: addEventListenerなどを使用して、DOM要素に対して直接イベントリスナーを設定する場合。(ただし、これらのリスナーは後述するbeforeUnmountで必ず解除する必要があります)
  • 注意点: サーバーサイドレンダリング(SSR)の場合、このフックはクライアント側でのみ実行されます。SSR環境ではDOMが存在しないためです。また、mountedはコンポーネントが初めてDOMにマウントされたときに一度だけ呼ばれます。その後のデータ変更によるDOM更新では呼ばれません(それはupdatedの役割です)。

(図解イメージにおける位置: Create VDOM... の直後)

Mounting段階のまとめとして、beforeMountはあまり使いませんが、mountedはコンポーネントが画面に表示され、DOMアクセスが必要な処理を行うための主要なフックです。

Updating Phase(更新段階): コンポーネントの変化

コンポーネントのリアクティブデータ(datapropscomputedなど)が変更されると、Vueはその変更を検知し、必要に応じてコンポーネントを再レンダリングします。このプロセスがUpdating段階です。

5. beforeUpdate フック

  • 呼び出されるタイミング: リアクティブデータが変更され、再レンダリング(パッチ適用)が始まる直前に呼び出されます。仮想DOMは再生成される直前、実際のDOMはまだ更新されていません
  • インスタンスの状態:
    • データは既に新しい値に更新されています。
    • しかし、this.$elを通じてアクセスできる実際のDOMは、まだ変更前の古い状態を反映しています。
  • ユースケース:
    • DOMが更新される前の状態にアクセスしたい場合。例えば、スクロール位置を保存したり、特定のDOM要素のサイズや位置を記録したりする前に、変更後のデータに基づいて何かを計算したい場合など。
    • 再レンダリングが開始される前に、データ変更に応じた最後の準備を行う場合。
  • 注意点: このフック内でデータを変更すると、再レンダリングが再度トリガーされる可能性があります。無限更新ループに陥らないように、慎重に使用する必要があります。また、実際のDOMは古い状態なので、DOM操作は避けるべきです。

(図解イメージにおける位置: Data Change? (Yes) の直後、Re-render VDOM の直前)

javascript
// Options APIでの例
export default {
data() {
return {
count: 0,
};
},
beforeUpdate() {
console.log(`beforeUpdate: カウントが ${this.count} に変わります。DOMはまだ古い値`);
// この時点での this.$el は、変更前の count 値を反映している
},
methods: {
increment() {
this.count++;
},
},
};

beforeUpdateフックの実行後、Vueは新しいリアクティブデータに基づいてレンダリング関数を再実行し、新しい仮想DOMツリーを生成します。そして、その新しい仮想DOMツリーと古い仮想DOMツリーを比較(差分検出、diffing)し、必要最小限の変更だけを実際のDOMに適用(パッチ適用、patching)します。

6. updated フック

  • 呼び出されるタイミング: データ変更による再レンダリングが完了し、実際のDOMが最新の状態に更新された後に呼び出されます。
  • インスタンスの状態:
    • データは新しい値に更新されています。
    • this.$elを通じてアクセスできる実際のDOMも、最新のデータに基づいて更新されています
  • ユースケース:
    • DOMが更新された後の操作。例えば、リストが更新されて新しい要素が追加された後に特定の要素にフォーカスを当てる、スクロール位置を更新されたコンテンツに合わせて調整する、DOMの新しいサイズや位置に基づいて何かを計算するなど。
    • DOMが変更されたことをサードパーティライブラリに通知する必要がある場合。
      javascript
      updated() {
      console.log(`updated: カウントが ${this.count} に変わりました。DOMも更新済み`);
      // この時点での this.$el は、最新の count 値を反映している
      // DOMの更新後の操作を行う
      const updatedElement = this.$el.querySelector('.count-display');
      if (updatedElement) {
      // 例えば、更新された要素にアニメーションクラスを追加する
      // updatedElement.classList.add('highlight');
      }
      // サードパーティライブラリへの通知など
      // if (this.myPlugin) { this.myPlugin.update(); }
      },
      // ...
      // template: '<div class="count-display">{{ count }}</div>'
  • 注意点: updatedフック内でデータを変更すると、無限更新ループを引き起こす可能性があります。データ変更は、ユーザーインタラクションなど、明確なトリガーがある場合にのみ行うようにしてください。また、updatedフックは、データが変更されるたびに子コンポーネントの更新が完了するのを待たずに呼ばれる可能性があることに注意してください。全ての子コンポーネントのDOMが更新された後に何かを実行したい場合は、this.$nextTickを使用する必要があります。

(図解イメージにおける位置: Patch VDOM... の直後)

Updating段階のまとめとして、beforeUpdateは更新前の状態をチェックしたい場合に、updatedはDOM更新後の操作を行いたい場合に使用します。どちらのフックも、データ変更による無限ループに注意が必要です。

Destruction Phase(破棄段階): コンポーネントの死

コンポーネントがDOMから削除され、関連リソースが解放される段階です。例えば、v-ifディレクティブによってコンポーネントが表示されなくなったり、リストレンダリングされた要素が削除されたり、Vue Routerによって異なるルートに遷移したりした場合に発生します。

7. beforeDestroy (Vue 2) / beforeUnmount (Vue 3) フック

  • 呼び出されるタイミング: コンポーネントインスタンスが破棄される直前に呼び出されます。
  • インスタンスの状態:
    • インスタンスはまだ完全に機能しており、datamethodsなどにもアクセスできます。
    • イベントリスナーやウォッチャ、タイマーなどがまだアクティブな状態です。
    • コンポーネントはまだDOMから削除されていません(this.$elはまだ存在します)。
  • ユースケース: このフックは、コンポーネントが破棄される前に必要なクリーンアップ処理を行うための主要な場所です。
    • イベントリスナーの解除: addEventListenerで登録したDOMイベントリスナーや、window.addEventListenerなどでグローバルに登録したイベントリスナーを解除します。解除しないと、コンポーネントがDOMから消えてもリスナーは残り続け、意図しない動作やメモリリークの原因となります。
      javascript
      // Options APIでの例
      mounted() {
      this.scrollHandler = () => { /* スクロール処理 */ };
      window.addEventListener('scroll', this.scrollHandler);
      },
      beforeUnmount() { // Vue 3
      console.log('beforeUnmount: インスタンス破棄直前、クリーンアップ');
      // 登録したイベントリスナーを解除
      window.removeEventListener('scroll', this.scrollHandler);
      // タイマーのクリア
      // clearInterval(this.myTimer);
      // 購読の解除 (例えば WebSocket, RxJS など)
      // this.subscription.unsubscribe();
      // 外部リソースの解放
      // this.chartInstance.destroy(); // Chart.js など
      },
      // Vue 2 の場合:
      // beforeDestroy() {
      // console.log('beforeDestroy: インスタンス破棄直前、クリーンアップ');
      // window.removeEventListener('scroll', this.scrollHandler);
      // }
    • タイマー(setTimeout, setInterval)のクリア
    • 購読(Subscription)の解除: WebSocket接続、RxJS Observable、Vuexストアへの購読などを解除します。
    • 作成したサードパーティライブラリインスタンスの破棄: mountedで初期化したインスタンス(Chart.js, Mapbox GL JSなど)がdestroy()のようなクリーンアップメソッドを提供している場合、ここで呼び出します。
  • 注意点: Vueによって管理されているイベントリスナー(v-onディレクティブで登録されたもの)やウォッチャなどは、Vueが自動的にクリーンアップしてくれます。このフックで手動で解除する必要があるのは、自分でJavaScriptコード内で直接登録したものです。

(図解イメージにおける位置: Call $destroy()... の直後)

beforeDestroy / beforeUnmount フックの実行後、Vueはインスタンスの関連付けを解除する処理を開始します。ウォッチャ、子コンポーネント、ルートDOM要素に紐づけられたイベントリスナーやディレクティブなどが解除されます。そして、インスタンスはDOMから削除されます。

8. destroyed (Vue 2) / unmounted (Vue 3) フック

  • 呼び出されるタイミング: コンポーネントインスタンスが完全に破棄された後に呼び出されます。
  • インスタンスの状態:
    • インスタンスはもはや機能しておらず、Vueのリアクティビティシステムやイベントシステムは動作していません。
    • this.$elは存在しますが、コンポーネントのルート要素は既にDOMから削除されています。
    • ほとんどのバインディングやイベントリスナーは解除されています。
  • ユースケース: 非常に限られています。主にデバッグ目的で、インスタンスが完全に破棄されたことを確認するために使用されることがあります。ほとんどのクリーンアップ処理はbeforeDestroy / beforeUnmountで行うべきです。このフックが呼ばれる時点では、インスタンスはほぼ空っぽの状態であり、有用な操作はほとんどできません。
  • 注意点: この段階で何かをクリーンアップしようとしても、手遅れであるか、インスタンスの状態が不安定なため意図通りに動作しない可能性があります。

(図解イメージにおける位置: Teardown... の直後)

Destruction段階のまとめとして、最も重要なのはbeforeDestroy / beforeUnmountです。ここで、コンポーネント外部に影響を与えたり、メモリリークの原因となったりする可能性のあるリソースを適切に解放する必要があります。

その他のライフサイクルフック

Vue 2.6以降、特にkeep-aliveコンポーネントやエラーハンドリングに関連していくつかの追加のライフサイクルフックが導入されました。

9. activated / deactivated フック

  • 呼び出されるタイミング: <keep-alive> コンポーネントにラップされた動的コンポーネントが、アクティブ化(表示)されたとき (activated) または非アクティブ化(非表示)されたとき (deactivated) に呼び出されます。
  • ユースケース:
    • <keep-alive> は、コンポーネントインスタンスを破棄せずにキャッシュすることで、状態の維持とレンダリングパフォーマンスの向上を図ります。通常のコンポーネントは破棄時にbeforeUnmount/unmountedが呼ばれますが、<keep-alive>内のコンポーネントは非アクティブ化されるだけで破棄されないため、これらのフックは呼ばれません。代わりにdeactivatedが呼ばれます。
    • コンポーネントが再び表示される(アクティブ化される)際には、mountedは呼ばれず、代わりにactivatedが呼ばれます。
    • activated: キャッシュから復帰して表示されるたびに実行したい処理(例: データの再取得、スクロール位置の復元)。
    • deactivated: 非表示になるたびに実行したい処理(例: タイマーの一時停止、動画再生の停止)。
  • 注意点: これらのフックは、<keep-alive>でラップされていない通常のコンポーネントでは呼び出されません。

javascript
// Options APIでの例
export default {
activated() {
console.log('activated: keep-alive コンポーネントがアクティブになりました');
// データ再フェッチや表示に必要な処理
},
deactivated() {
console.log('deactivated: keep-alive コンポーネントが非アクティブになりました');
// タイマー停止やリソースの一時停止
},
// ... 他のライフサイクルフックは通常のコンポーネントと同様に呼ばれる
// (ただし、mounted/unmountedは初回のマウント/破棄時のみ)
};

10. errorCaptured フック

  • 呼び出されるタイミング: 子孫コンポーネントから伝播されたエラーをキャッチしたときに呼び出されます。このフックは、エラーがコンポーネント階層をバブリングしていく際に、どの親コンポーネントでも定義できます。
  • インスタンスの状態: エラーを発生させたコンポーネント(vm)、エラーオブジェクト(err)、エラーが発生した場所を示す情報文字列(info)が引数として渡されます。
  • ユースケース:
    • エラー境界(Error Boundaries) の実装。子コンポーネントで発生したエラーを親コンポーネントでキャッチし、エラーメッセージを表示したり、代替コンテンツをレンダリングしたりします。これにより、子コンポーネントのエラーがアプリケーション全体をクラッシュさせるのを防ぎます。
    • エラー報告サービス(Sentry, Bugsnagなど)へのエラーレポート送信。
  • 戻り値: このフックがfalseを返すと、エラーの伝播が停止されます。trueを返したり何も返さない場合、エラーはさらに上の親コンポーネントに伝播されます。
  • 注意点: このフックは、非同期コールバックやDOMイベントハンドラー内で発生したエラーはキャッチしません(それらはブラウザの標準エラーハンドリングに委ねられます)。主に、Vueのレンダリング関数やライフサイクルフック内で発生したエラーを対象とします。

“`javascript
// Options APIでの例
export default {
errorCaptured(err, vm, info) {
console.error(‘errorCaptured: 子孫コンポーネントからエラーをキャッチしました’);
console.error(‘エラー:’, err);
console.error(‘エラー発生コンポーネント:’, vm);
console.error(‘エラー情報:’, info);

// エラー報告サービスに送信
// MyErrorReporter.report(err, { component: vm.$options.name, info: info });

// エラー表示用のデータフラグをセットするなど
this.hasError = true;

// エラーの伝播を停止したい場合は false を返す
// return false;

},
data() {
return {
hasError: false,
};
},
// …
template: <div>
<div v-if="hasError">エラーが発生しました。</div>
<child-component v-else />
</div>
,
};
“`

これらの追加フックは、特定の高度なシナリオ(キャッシュ、エラー回復)に対応するために提供されています。

Composition APIとライフサイクルフック

Vue 3で導入されたComposition APIを使用する場合、ライフサイクルフックの利用方法が変わります。Options APIのようにコンポーネントオプションとして定義するのではなく、setup関数内で対応する関数をインポートして使用します。

Options API Composition API (Vue 3)
beforeCreate N/A (setup() の実行前)
created setup() の実行後
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured

Composition APIでは、これらのフック関数をvueまたは@vue/runtime-coreからインポートし、setup関数内で呼び出します。

“`javascript
// Composition APIでの例
import { ref, onMounted, onBeforeUnmount, onUpdated, onErrorCaptured } from ‘vue’;

export default {
setup() {
const count = ref(0);

console.log('setup: created フックに相当するタイミングです。');

// Options API の beforeCreate に相当する処理は setup() の先頭で行う

onMounted(() => {
  console.log('onMounted: DOMにマウントされました');
  // DOMアクセスが必要な処理
});

onUpdated(() => {
  console.log('onUpdated: DOMが更新されました');
});

onBeforeUnmount(() => {
  console.log('onBeforeUnmount: インスタンス破棄直前、クリーンアップ');
  // イベントリスナー解除などのクリーンアップ
});

onUnmounted(() => {
  console.log('onUnmounted: インスタンスが完全に破棄されました');
});

onErrorCaptured((err, vm, info) => {
  console.error('onErrorCaptured:', err, vm, info);
  // エラーハンドリング
});

// リアクティブデータの変更
const increment = () => {
  count.value++;
};

// Options API の beforeCreate/created に相当する非同期データフェッチなどは、
// setup() 内で直接行うか、await を使う。
// async function fetchData() { ... }
// fetchData();

return {
  count,
  increment,
};

},
};
“`

Composition APIの大きな利点は、関連するロジック(データ、メソッド、算出プロパティ、ウォッチャ、そしてライフサイクルフックでのクリーンアップなど)を、ライフサイクルフックの定義場所に散らばらせることなく、論理的にまとめることができる点です。例えば、ある機能のために設定したイベントリスナーの登録と解除を、onMountedonBeforeUnmountに分けて書くのではなく、同じカスタムフックやセットアップブロック内にまとめて記述することで、コードの可読性と保守性が向上します。

サーバーサイドレンダリング (SSR) とライフサイクル

Vue.jsをサーバーサイドレンダリング(SSR)で使用する場合、ライフサイクルフックの実行タイミングがクライアントサイドのみの場合と異なります。サーバー上ではDOMが存在しないため、DOMに依存するフックは実行されません。

サーバー上で実行されるフック:

  • beforeCreate
  • created

これらのフックは、サーバー上でコンポーネントインスタンスが作成される際に実行されます。特にcreatedは、SSR中にコンポーネントに必要な初期データをフェッチするのに適した場所とされています(Vue SSRガイドでは、asyncDataオプションやComposition APIのsetupトップレベルawaitなどが推奨される方法ですが、createdもデータフェッチの手段として利用可能です)。

サーバー上で実行されないフック:

  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy (Vue 2) / beforeUnmount (Vue 3)
  • destroyed (Vue 2) / unmounted (Vue 3)
  • activated
  • deactivated
  • errorCaptured (一部のSSRフレームワークではキャッチできる場合もありますが、基本的なVue SSRではクライアントサイドのみ)

これらのフックはDOM操作やDOM状態に依存するため、DOMが存在しないサーバー環境では意味をなしません。コンポーネントがクライアントサイドでハイドレーション(静的なサーバーレンダリングされたHTMLにクライアントサイドのインタラクティビティを付加するプロセス)された後、クライアントサイドで通常のライフサイクルフローが再開されます。

SSRを考慮した開発では、createdフックでデータフェッチを行う場合、そのデータがサーバーとクライアントの両方で適切に利用可能であることを確認する必要があります。また、DOMアクセスを伴う処理は必ずmountedフックで行うようにし、SSR時にはスキップされるように記述する必要があります。

よくある間違いと注意点

ライフサイクルを理解していても、使い方を誤ると様々な問題が発生します。ここでは、よくある間違いとその注意点を見ていきましょう。

  1. created でDOM操作を試みる:
    • 間違い: created フック内で this.$el にアクセスしたり、DOM要素のサイズを取得したり、サードパーティのDOM操作ライブラリを初期化しようとしたりすること。
    • 理由: created の時点ではコンポーネントはまだDOMにマウントされていません。this.$el は存在しないか、未コンパイルの状態です。
    • 対策: DOM操作が必要な処理は、必ずコンポーネントがDOMにマウントされた後の mounted フックで行う。
  2. mounted でデータ取得を行う:
    • 間違い: コンポーネントの表示に必要な主要なデータを mounted フックで取得すること。
    • 理由: mounted が呼ばれるのは、コンポーネントの初期DOMが既に描画された後です。データ取得が非同期である場合、初期表示時にはデータが表示されず、データが取得された後に初めて内容が表示されるため、ユーザー体験が悪くなる可能性があります(データのちらつき、ローディング表示の遅延など)。また、SSR時にはmountedは呼ばれないため、サーバーサイドでデータを取得できません。
    • 対策: コンポーネントの表示に必要な初期データは、created (Options API) または setup のトップレベル await (Composition API) で取得するのが一般的です。これにより、データ取得とコンポーネントの初期化が同時に進み、データが揃った状態でDOMマウント後の処理(もしあれば)に進めます。
  3. updated フック内での安易なデータ変更:
    • 間違い: updated フック内で、特定の条件を満たすたびにリアクティブデータを変更する処理。
    • 理由: updated はデータ変更によるDOM更新のたびに呼ばれます。updated 内でデータを変更すると、それが原因で再度更新がトリガーされ、無限に beforeUpdate -> updated のサイクルが繰り返される可能性があります。
    • 対策: updated は主にDOM更新後の状態を利用するためのものです。データ変更が必要な場合は、ユーザー操作や外部イベントなど、明確なトリガーに基づいて行うようにし、無限ループにならないよう条件分岐を厳密にするか、別の手段(watchなど)を検討する。
  4. 手動で登録したイベントリスナーやタイマーのクリーンアップ忘れ:
    • 間違い: mountedなどで window.addEventListenersetInterval を使用して登録したイベントリスナーやタイマーを、コンポーネント破棄時に解除しないこと。
    • 理由: コンポーネントがDOMから削除されても、登録されたリスナーやタイマーはJavaScriptのメモリ上に残り続けます。リスナー関数がコンポーネントインスタンスへの参照を持っている場合、コンポーネントインスタンス自体もガーベージコレクションされずに残り続け、メモリリークが発生します。タイマーも同様に意図せず動き続ける可能性があります。
    • 対策: mountedなどで手動で登録したリソースは、対応する beforeDestroy (Vue 2) / beforeUnmount (Vue 3) フックで必ず解除処理を行う(removeEventListener, clearInterval, clearTimeout, unsubscribe, destroy() など)。
  5. 子コンポーネントのライフサイクルと親コンポーネントのライフサイクルの非同期性の誤解:
    • 注意点: 親コンポーネントの mounted が呼ばれた時点でも、全ての子コンポーネントの mounted が完了しているとは限りません。非同期にレンダリングされる子コンポーネント(例: v-if や動的コンポーネントによる遅延レンダリング)がある場合、親の mounted の後で子の mounted が呼ばれます。
    • 対策: 特定の子コンポーネントが完全にマウントされた後に親側で何かをしたい場合は、子コンポーネントから親にイベントを発行してもらうなどの連携手段を検討する。あるいは、DOM更新が完全に反映された後の処理には this.$nextTick を利用する。
  6. this.$nextTick の理解不足:
    • 注意点: this.$nextTick(callback) は、DOMが最新の状態に更新された後にコールバックを実行するためのユーティリティメソッドです。リアクティブデータの変更によってDOMがすぐに更新されるわけではなく、Vueは変更をバッファリングし、次のイベントループのティックでまとめてDOM更新を行います。
    • ユースケース: data を変更した直後に、その変更が反映されたDOMにアクセスしたい場合。例えば、リストにアイテムを追加した後、その新しいアイテムの要素のサイズを取得したい場合など。
    • 関連: updated フックはDOM更新後に呼ばれますが、this.$nextTick は任意のタイミングでデータの変更を待ってからDOMアクセスを保証します。特に、複数のデータ変更が一つの更新サイクルにバッファリングされる場合など、updated よりも this.$nextTick の方が正確に目的のタイミングを捉えられることがあります。

これらの注意点を頭に入れておくことで、ライフサイクルフックをより安全かつ効果的に利用できます。

まとめ:コンポーネントの一生を理解する重要性

Vue.jsコンポーネントのライフサイクルを追う旅は、Creation、Mounting、Updating、Destructionという主要な段階を経てきました。それぞれの段階には、コンポーネントの状態に応じた適切な処理を実行するためのライフサイクルフックが用意されています。

  • beforeCreate: インスタンス初期化直後、データ・メソッド未準備。ほとんど使わない。
  • created: インスタンス生成、データ・メソッド準備完了、DOM未マウント。初期データのフェッチに最適。
  • beforeMount: テンプレートコンパイル後、DOMマウント直前。あまり使わない。
  • mounted: DOMに完全にマウントされた後。DOM操作、サードパーティライブラリ初期化に最適。
  • beforeUpdate: データ変更、再レンダリング直前。更新前のDOM状態確認など。
  • updated: 再レンダリング、DOM更新完了後。更新後のDOM操作など。無限ループに注意。
  • beforeDestroy / beforeUnmount: インスタンス破棄直前。手動で登録したリソースのクリーンアップに必須。メモリリーク防止。
  • destroyed / unmounted: インスタンス完全破棄後。ほとんど使わない。

さらに、<keep-alive>利用時の activated / deactivated や、エラー境界としての errorCaptured といった、特定のシナリオに対応するフックも存在します。Composition APIを使用する場合は、対応する onXxx 関数を利用します。

コンポーネントのライフサイクルと各フックの役割を深く理解することは、単に知識が増えるだけでなく、開発の質を大きく向上させます。

  • デバッグの効率化: 問題が発生した際に、どのライフサイクル段階で何が起きているかを推測しやすくなります。
  • パフォーマンスの最適化: 不要な処理を適切なフックの外で実行するのを避け、必要な処理を最適なタイミングで行うことで、アプリケーションの応答性を向上させることができます。
  • 堅牢なコードの記述: メモリリークを防ぐためのクリーンアップ処理を忘れずに行うなど、コンポーネントの寿命全体を通して正しく機能するコードを書くことができます。
  • 複雑な機能の実装: サードパーティライブラリとの連携や、高度なエラーハンドリングなど、特定のライフサイクルイベントに依存する機能を自信を持って実装できます。

Vue.jsコンポーネントは、私たちが構築するUIの一部品ですが、その裏では独自の生命サイクルを刻んでいます。この「一生」を理解し、各段階で適切なフックを用いてコードを書くことで、Vue.jsの強力な機能を最大限に引き出し、より洗練されたアプリケーションを開発できるようになるでしょう。

この記事が、あなたのVue.jsライフサイクルに関する理解を深め、今後の開発に役立つことを願っています。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール