- Vue.js
Vueライフサイクル一覧|onMounted・onUnmountedの順番
Vueのライフサイクルとは、コンポーネントが作られてから画面に表示され、更新され、取り除かれるまでの一連の段階のことです。 それぞれの段階で呼ばれる関数をライフサイクルフックと呼び、Composition API では onMounted(画面に表示された後)や onUnmounted(取り除かれた後)のように on で始まる関数に処理を登録します。
この記事では、Composition API の全12フックを一覧表にし、親・子・孫の3階層でどの順に呼ばれるかを Vue 3.5.43 で実際に動かして確かめました。マウント・更新・アンマウント・v-if の切り替え・KeepAlive のそれぞれについて、番号つきのログをそのまま掲載しています。onMounted でのデータ取得と onUnmounted での解除は、コピーして使える型として用意しました。
Vueのライフサイクルフックの早見表
「サーバー」の欄は、サーバーサイドレンダリング(SSR)中に呼ばれるかどうかです。フックの種類と SSR での扱いは、Vue 公式の API リファレンス(Composition API: Lifecycle Hooks、今回参照)に基づいています。
| フック | 発火タイミング | 主な用途 | Options API の対応名 | サーバー |
|---|---|---|---|---|
| (setup) | コンポーネントの作成時、最初に1回 | 状態の定義、フックの登録 | beforeCreate / created | 呼ばれる |
| onBeforeMount | 最初の描画の直前 | 利用場面は少ない | beforeMount | ― |
| onMounted | DOM に挿入された後 | データ取得、DOM 参照、リスナーやタイマーの登録 | mounted | ― |
| onBeforeUpdate | 状態が変わり、再描画する直前 | 更新前の DOM の状態を読む | beforeUpdate | ― |
| onUpdated | 再描画の後 | 更新後の DOM を読む | updated | ― |
| onBeforeUnmount | 取り除かれる直前 | DOM がまだある状態での後始末 | beforeUnmount | ― |
| onUnmounted | 取り除かれた後 | リスナー・タイマー・接続の解除 | unmounted | ― |
| onActivated | KeepAlive のキャッシュから表示に戻ったとき(初回表示も含む) | 再表示時の再取得、タイマー再開 | activated | ― |
| onDeactivated | KeepAlive のキャッシュに退避されたとき | タイマーの一時停止 | deactivated | ― |
| onErrorCaptured | 子孫コンポーネントのエラーを捕まえたとき | エラー表示への切り替え、記録 | errorCaptured | 呼ばれる |
| onServerPrefetch | サーバーで描画する前(サーバー専用) | SSR でのデータ取得 | serverPrefetch | 呼ばれる |
| onRenderTracked | 描画中に依存が記録されたとき(開発時のみ) | 再描画の原因調査 | renderTracked | ― |
| onRenderTriggered | 依存の変更で再描画が起きたとき(開発時のみ) | 再描画の原因調査 | renderTriggered | ― |
実行順の早見表(親・子・孫の3階層)
| 場面 | 実行される順番 |
|---|---|
| マウント | 親 setup → 親 onBeforeMount → 子 setup → 子 onBeforeMount → 孫 setup → 孫 onBeforeMount → 孫 onMounted → 子 onMounted → 親 onMounted |
| 更新(props が孫まで届く変更) | 親 onBeforeUpdate → 子 onBeforeUpdate → 孫 onBeforeUpdate → 孫 onUpdated → 子 onUpdated → 親 onUpdated |
| アンマウント | 親 onBeforeUnmount → 子 onBeforeUnmount → 孫 onBeforeUnmount → 孫 onUnmounted → 子 onUnmounted → 親 onUnmounted |
「before 系は親から子へ、完了系(Mounted・Updated・Unmounted)は子から親へ」と覚えると、すべての場面に当てはまります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Vueライフサイクル一覧|onMounted・onUnmountedの順番」
検証した環境と確認の方法
| 項目 | バージョン |
|---|---|
| Vue / @vue/server-renderer | 3.5.43 |
| @vue/test-utils | 2.5.1 |
| jsdom | 30.1.1 |
| Node.js | 22.22.2 |
<script setup> の SFC を @vue/compiler-sfc でコンパイルし、Vue Test Utils で jsdom 上にマウントして、console.log の順番を記録しました。ブラウザではなく jsdom 上での実行結果です。エラー処理と SSR の例は、defineComponent と h() で書いたコンポーネントで確認しています。
ライフサイクルの全体の流れ
flowchart TD
A[setup 実行] --> B[onBeforeMount]
B --> C[最初の描画・DOM に挿入]
C --> D[onMounted]
D --> E{状態が変わる}
E -->|変わった| F[onBeforeUpdate]
F --> G[再描画]
G --> H[onUpdated]
H --> E
E -->|取り除かれる| I[onBeforeUnmount]
I --> J[DOM から削除]
J --> K[onUnmounted]
Composition API には beforeCreate と created に当たるフックがありません。その段階で行いたい処理は、setup(script setup のトップレベル)に直接書きます。
3階層で実行順を確かめるコード
フックの登録をひとつの関数にまとめ、親・子・孫で同じものを呼びます。
// useLog.js
import {
onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted, onActivated, onDeactivated,
} from 'vue'
let i = 0
const out = (s) => console.log(String(++i).padStart(2) + '. ' + s)
export function useLog(name) {
out(name + ' setup')
onBeforeMount(() => out(name + ' onBeforeMount'))
onMounted(() => out(name + ' onMounted'))
onBeforeUpdate(() => out(name + ' onBeforeUpdate'))
onUpdated(() => out(name + ' onUpdated'))
onBeforeUnmount(() => out(name + ' onBeforeUnmount'))
onUnmounted(() => out(name + ' onUnmounted'))
onActivated(() => out(name + ' onActivated'))
onDeactivated(() => out(name + ' onDeactivated'))
}
<!-- GrandChild.vue(孫) -->
<script setup>
import { useLog } from './useLog.js'
defineProps(['n'])
useLog('孫')
</script>
<template><em>孫 n={{ n }}</em></template>
<!-- Child.vue(子) -->
<script setup>
import { ref } from 'vue'
import { useLog } from './useLog.js'
import GrandChild from './GrandChild.vue'
defineProps(['n'])
const own = ref(0)
defineExpose({ own })
useLog('子')
</script>
<template>
<div>子 n={{ n }} own={{ own }} <GrandChild :n="n" /></div>
</template>
<!-- Parent.vue(親) -->
<script setup>
import { ref } from 'vue'
import { useLog } from './useLog.js'
import Child from './Child.vue'
const n = ref(0)
const show = ref(true)
const title = ref('t')
const child = ref(null)
defineExpose({ n, show, title, child })
useLog('親')
</script>
<template>
<h1>{{ title }}</h1>
<Child v-if="show" ref="child" :n="n" />
</template>
検証では、3つのコンポーネントを1つのスクリプトの中でコンパイルして読み込んだため、import の部分はグローバル変数経由の受け渡しに置き換えています。フックの登録とテンプレートは上のコードと同じです。番号は場面ごとに 1 から振り直しています。
マウント時の実行順
1. 親 setup
2. 親 onBeforeMount
3. 子 setup
4. 子 onBeforeMount
5. 孫 setup
6. 孫 onBeforeMount
7. 孫 onMounted
8. 子 onMounted
9. 親 onMounted
親の描画の途中で子が作られ、子の描画の途中で孫が作られます。いちばん内側の孫が DOM になってから、外側に向かって onMounted が呼ばれます。
| ここからわかること | 役立つ場面 |
|---|---|
| 親の onMounted の時点で、子と孫の DOM はそろっている | 親から子の要素の大きさを測る、子の公開メソッドを呼ぶ |
| 子の setup は親の setup より後 | 親で provide した値を子で inject できる |
| 子の onMounted は親の onMounted より前 | 子の onMounted から、親の onMounted で用意する値は使えない |
更新時の実行順
更新は「どのコンポーネントの描画に使われている値が変わったか」で、呼ばれる範囲が変わります。3通りを試しました。
親のnを変更(propsが孫まで届く)
1. 親 onBeforeUpdate
2. 子 onBeforeUpdate
3. 孫 onBeforeUpdate
4. 孫 onUpdated
5. 子 onUpdated
6. 親 onUpdated
親だけが表示に使うtitleを変更
1. 親 onBeforeUpdate
2. 親 onUpdated
子だけが持つownを変更
1. 子 onBeforeUpdate
2. 子 onUpdated
| 変更した値 | 更新フックが呼ばれたコンポーネント |
|---|---|
| 親の n(props で子・孫に渡している) | 親・子・孫 |
| 親の title(親のテンプレートだけで使用) | 親のみ |
| 子の own(子のテンプレートだけで使用) | 子のみ |
onUpdated は「そのコンポーネント自身の描画に関わる値が変わったとき」だけ呼ばれます。 親が更新されても、渡している props が変わらなければ子の更新フックは呼ばれません。逆に、子の中だけの変更では親のフックは呼ばれません。
onUpdated は「どの値が変わったか」を教えてくれないので、特定の値の変化に反応したいときは watch が合っています。onUpdated の中で状態を書き換えると、再び更新が起こり続けることがあるため、DOM を読む用途にとどめると扱いやすくなります。
アンマウント時とv-if切り替え時の実行順
親をアンマウント
1. 親 onBeforeUnmount
2. 子 onBeforeUnmount
3. 孫 onBeforeUnmount
4. 孫 onUnmounted
5. 子 onUnmounted
6. 親 onUnmounted
子をv-ifでfalseにする
1. 親 onBeforeUpdate
2. 子 onBeforeUnmount
3. 孫 onBeforeUnmount
4. 孫 onUnmounted
5. 子 onUnmounted
6. 親 onUpdated
v-ifをtrueに戻す
1. 親 onBeforeUpdate
2. 子 setup
3. 子 onBeforeMount
4. 孫 setup
5. 孫 onBeforeMount
6. 孫 onMounted
7. 子 onMounted
8. 親 onUpdated
v-if の切り替えは、親から見ると「更新」、子から見ると「アンマウントとマウント」です。true に戻したときは子の setup からやり直しになり、子が持っていた状態(own など)は初期値に戻ります。 状態を保ったまま表示だけ切り替えたいときは、v-show か、次の KeepAlive を使います。
| 切り替え方 | 子のフック | 子の状態 |
|---|---|---|
| v-if | 毎回アンマウントとマウント | 初期化される |
| v-show | 呼ばれない(表示・非表示の切り替えのみ。今回は未実行) | 保たれる |
| KeepAlive + v-if | onDeactivated / onActivated | 保たれる |
KeepAliveでの実行順(onActivated・onDeactivated)
KeepAlive は、表示から外れたコンポーネントを破棄せずにキャッシュしておく組み込みコンポーネントです。親のテンプレートを次のように変えて確かめました。
<!-- ParentKeepAlive.vue -->
<script setup>
import { ref } from 'vue'
import { useLog } from './useLog.js'
import Child from './Child.vue'
const show = ref(true)
defineExpose({ show })
useLog('親')
</script>
<template>
<KeepAlive>
<Child v-if="show" :n="1" />
</KeepAlive>
</template>
マウント
1. 親 setup
2. 親 onBeforeMount
3. 子 setup
4. 子 onBeforeMount
5. 孫 setup
6. 孫 onBeforeMount
7. 孫 onMounted
8. 子 onMounted
9. 孫 onActivated
10. 子 onActivated
11. 親 onMounted
v-ifをfalseにする
1. 親 onBeforeUpdate
2. 孫 onDeactivated
3. 子 onDeactivated
4. 親 onUpdated
v-ifをtrueに戻す
1. 親 onBeforeUpdate
2. 孫 onActivated
3. 子 onActivated
4. 親 onUpdated
親をアンマウント
1. 親 onBeforeUnmount
2. 子 onBeforeUnmount
3. 孫 onBeforeUnmount
4. 孫 onDeactivated
5. 子 onDeactivated
6. 孫 onUnmounted
7. 子 onUnmounted
8. 親 onUnmounted
| ログからわかること | 意味 |
|---|---|
| 初回マウントでも onActivated が呼ばれる | 「表示されるたびに行う処理」は onActivated に書けば初回も含まれる |
| 切り替え時に setup・onMounted・onUnmounted が呼ばれない | コンポーネントは破棄されず、状態が保たれている |
| KeepAlive の直下の子だけでなく孫でも呼ばれる | キャッシュされたツリーの中の全コンポーネントが対象 |
| 親のアンマウント時は onDeactivated の後に onUnmounted | 最終的な解除は onUnmounted に書いておけば実行される |
KeepAlive の中では onUnmounted が切り替えのたびには呼ばれない点が大事です。タイマーやポーリングを「表示中だけ動かしたい」ときは、onActivated で開始し onDeactivated で止めます。
<script setup>
import { onActivated, onDeactivated } from 'vue'
let timerId
onActivated(() => {
timerId = setInterval(() => { /* 最新の状態を取得 */ }, 5000)
})
onDeactivated(() => {
clearInterval(timerId)
})
</script>
(このタイマーの例は書き方の紹介で、単体では未実行です。onActivated と onDeactivated が呼ばれる順番は上のログで確認しています。)
Options APIのフックと併用したときの順番
移行の途中などで、Options API と Composition API のフックが同じコンポーネントに並ぶことがあります。
import { defineComponent, h, onBeforeMount, onMounted } from 'vue'
export default defineComponent({
setup() {
console.log('setup')
onBeforeMount(() => console.log('onBeforeMount (Composition)'))
onMounted(() => console.log('onMounted (Composition)'))
},
beforeCreate() { console.log('beforeCreate') },
created() { console.log('created') },
beforeMount() { console.log('beforeMount (Options)') },
mounted() { console.log('mounted (Options)') },
render: () => h('i'),
})
1. setup
2. beforeCreate
3. created
4. onBeforeMount (Composition)
5. beforeMount (Options)
6. onMounted (Composition)
7. mounted (Options)
setup は beforeCreate よりも前に実行されます。 同じ段階のフックは、Composition API で登録したものが先、Options API のものが後でした。
| Options API | Composition API |
|---|---|
| beforeCreate / created | setup の中に直接書く |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount(Vue 2 では beforeDestroy) | onBeforeUnmount |
| unmounted(Vue 2 では destroyed) | onUnmounted |
| activated / deactivated | onActivated / onDeactivated |
| errorCaptured | onErrorCaptured |
| serverPrefetch | onServerPrefetch |
onMountedでデータ取得、onUnmountedで解除するコピペ用の型
データ取得・リスナー・タイマーをひとつにまとめた型です。
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const items = ref([])
const loading = ref(true)
const error = ref('')
const width = ref(0)
const seconds = ref(0)
const controller = new AbortController()
let timerId
const onResize = () => { width.value = window.innerWidth }
onMounted(async () => {
window.addEventListener('resize', onResize)
timerId = setInterval(() => { seconds.value++ }, 1000)
onResize()
try {
const res = await fetch('/api/items', { signal: controller.signal })
if (!res.ok) throw new Error('HTTP ' + res.status)
items.value = await res.json()
} catch (e) {
if (e.name !== 'AbortError') error.value = e.message
} finally {
loading.value = false
}
})
onUnmounted(() => {
window.removeEventListener('resize', onResize)
clearInterval(timerId)
controller.abort()
})
</script>
<template>
<p v-if="loading">読み込み中…</p>
<p v-else-if="error">取得できませんでした: {{ error }}</p>
<ul v-else><li v-for="item in items" :key="item.id">{{ item.title }}</li></ul>
<p>幅 {{ width }}px / {{ seconds }}秒</p>
</template>
fetch を「20ミリ秒後に2件を返し、abort されたら AbortError を投げる」関数に差し替え、addEventListener と setInterval の登録数を数えながら動かした結果です。
マウント直後: 画面=読み込み中…幅 1024px / 0秒 / リスナー=1 タイマー=1
取得完了後: 画面=備品A備品B幅 1024px / 0秒
アンマウント後: リスナー=0 タイマー=0
取得中にアンマウント: abort 回数=2 / リスナー=0 タイマー=0
アンマウント後にリスナーとタイマーの数が 0 に戻っています。最後の行は、取得が終わる前にアンマウントした場合で、エラー表示にはならず静かに終了しました(abort 回数の 2 は、1回目のアンマウントと合わせた累計です)。実際の API への通信は行っていません。
登録と解除の対応表
| onMounted で始めたもの | onUnmounted で行う解除 |
|---|---|
| window.addEventListener('resize', fn) | window.removeEventListener('resize', fn) ― 同じ関数を渡す |
| setInterval / setTimeout | clearInterval / clearTimeout |
| fetch | AbortController の abort() |
| WebSocket / EventSource | close() |
| IntersectionObserver / ResizeObserver | disconnect() |
| 外部ライブラリのインスタンス(グラフ、地図など) | そのライブラリの破棄メソッド |
removeEventListener には、登録時と同じ関数を渡す必要があります。アロー関数をその場に書くと別の関数になるため、上の型のように変数(onResize)に入れておくのがポイントです。
登録と解除をコンポーザブルにまとめる
同じ組み合わせを何度も書くときは、関数にまとめると解除の書き忘れがなくなります。
// useEventListener.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, handler) {
onMounted(() => target.addEventListener(event, handler))
onUnmounted(() => target.removeEventListener(event, handler))
}
<script setup>
import { ref } from 'vue'
import { useEventListener } from './useEventListener.js'
const width = ref(window.innerWidth)
useEventListener(window, 'resize', () => { width.value = window.innerWidth })
</script>
(このコンポーザブルの例は単体では未実行です。フックを関数の中から登録できることは、先ほどの useLog で確認しています。)
データ取得はsetupとonMountedのどちらに書くか
| 書く場所 | 特徴 | 向いている場面 |
|---|---|---|
| setup(script setup のトップレベル)で開始 | onMounted より早く通信を始められる。SSR でも実行される | DOM を使わない取得 |
| onMounted | DOM がそろっている。サーバーでは実行されない | 要素の大きさに応じた取得、ブラウザ専用の API を使う処理 |
| watch / watchEffect(immediate) | 条件が変わったら取り直せる | 検索条件やページ番号に連動する取得 |
テンプレート参照が使えるタイミングとwatchとの前後関係
DOM 要素への参照(テンプレート参照)がいつから使えるか、更新時に watch とフックがどの順で呼ばれるかを確かめます。
<script setup>
import {
ref, useTemplateRef, watch,
onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
} from 'vue'
const box = useTemplateRef('box')
const n = ref(0)
defineExpose({ n })
console.log('setup: box=' + box.value)
onBeforeMount(() => console.log('onBeforeMount: box=' + box.value))
onMounted(() => console.log('onMounted: box=' + box.value.tagName + ' 画面=' + box.value.textContent))
watch(n, () => console.log('watch(既定 pre): 画面=' + box.value.textContent))
watch(n, () => console.log('watch(post): 画面=' + box.value.textContent), { flush: 'post' })
onBeforeUpdate(() => console.log('onBeforeUpdate: 画面=' + box.value.textContent))
onUpdated(() => console.log('onUpdated: 画面=' + box.value.textContent))
</script>
<template><p ref="box">{{ n }}</p></template>
実行結果(マウント後に n を 1 増やす。「代入直後」と「nextTick 後」の行は、コンポーネントの外側のテストコードから出力したものです):
1. setup: box=null
2. onBeforeMount: box=null
3. onMounted: box=P 画面=0
-- n++
1. 代入直後(同期): 画面=0
2. watch(既定 pre): 画面=0
3. onBeforeUpdate: 画面=0
4. watch(post): 画面=1
5. onUpdated: 画面=1
6. nextTick 後: 画面=1
| タイミング | テンプレート参照 | 画面の内容 |
|---|---|---|
| setup / onBeforeMount | null | まだない |
| onMounted 以降 | 要素が入っている | 表示済み |
| 値を代入した直後(同期) | ― | 更新前のまま |
| watch(既定)/ onBeforeUpdate | ― | 更新前 |
| watch(flush: 'post')/ onUpdated / nextTick 後 | ― | 更新後 |
DOM 要素に触る処理は onMounted 以降に書く、値を変えた直後に更新後の DOM を読みたいときは await nextTick() を挟む、の2点がここからわかります。
エラーを受け止めるonErrorCaptured
onErrorCaptured は、子孫コンポーネントで起きたエラーを親側で受け取るフックです。
import { defineComponent, h, ref, onMounted, onErrorCaptured } from 'vue'
const Bad = defineComponent({
setup() {
onMounted(() => { throw new Error('子の onMounted で例外') })
return () => h('i', 'bad')
},
})
const Mid = defineComponent({
setup() {
onErrorCaptured((err, instance, info) => {
console.log(`中間 onErrorCaptured: ${err.message} / info=${info}`)
})
return () => h(Bad)
},
})
const Top = defineComponent({
setup() {
const msg = ref('')
onErrorCaptured((err) => {
console.log(`最上位 onErrorCaptured: ${err.message}`)
msg.value = '表示に問題が起きました'
return false // ここで伝播を止める
})
return () => h('div', [msg.value, h(Mid)])
},
})
Top が false を返す場合の実行結果(app.config.errorHandler も設定済み):
1. 中間 onErrorCaptured: 子の onMounted で例外 / info=mounted hook
2. 最上位 onErrorCaptured: 子の onMounted で例外
3. 画面: 表示に問題が起きましたbad
Top が何も返さない場合:
1. 中間 onErrorCaptured: 子の onMounted で例外 / info=mounted hook
2. 最上位(return なし): 子の onMounted で例外
3. app.config.errorHandler: 子の onMounted で例外 / info=mounted hook
| ログからわかること | 使い方 |
|---|---|
| エラーは近い祖先から順に、上へ伝わる | 画面の区画ごとに受け止める場所を決められる |
| false を返すと、それより上(app.config.errorHandler を含む)には伝わらない | その場で処理し終えたエラーは false で止める |
| 第3引数 info に発生箇所(mounted hook)が入る | 記録に残すと原因の特定が早くなる |
2つ目の例では、Vue Test Utils がエラーを再スローしたため、テストコード側で try / catch しています。onErrorCaptured が受け取るのは子孫のエラーで、自分自身のコンポーネントで起きたエラーは対象外です。
SSRで呼ばれるフック(onServerPrefetch)
@vue/server-renderer の renderToString で、サーバー側の描画を確かめました。
import { createSSRApp, defineComponent, h, ref,
onServerPrefetch, onBeforeMount, onMounted, onUnmounted } from 'vue'
import { renderToString } from '@vue/server-renderer'
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
const App = defineComponent({
setup() {
const data = ref(null)
console.log('setup')
onServerPrefetch(async () => {
console.log('onServerPrefetch 開始')
await sleep(10) // データ取得の代わり
data.value = '記事3件'
console.log('onServerPrefetch 完了')
})
onBeforeMount(() => console.log('onBeforeMount'))
onMounted(() => console.log('onMounted'))
onUnmounted(() => console.log('onUnmounted'))
return () => h('p', data.value ?? '読み込み中')
},
})
console.log('HTML: ' + await renderToString(createSSRApp(App)))
1. setup
2. onServerPrefetch 開始
3. onServerPrefetch 完了
4. HTML: <p>記事3件</p>
サーバーでは setup と onServerPrefetch だけが実行され、onBeforeMount・onMounted・onUnmounted は呼ばれていません。onServerPrefetch が返す Promise の完了を待ってから HTML が作られるため、取得したデータが入った状態で出力されています。同じ onServerPrefetch をクライアント側のマウントで登録した場合は、呼ばれませんでした。
| SSR を使うときの置き場所 | 理由 |
|---|---|
| window や document を使う処理は onMounted へ | サーバーでは呼ばれないので、ブラウザ専用の API を安心して使える |
| setup でタイマーやリスナーを登録しない | サーバーでは onUnmounted が呼ばれず、解除の機会がない |
| サーバーでのデータ取得は onServerPrefetch へ | 完了を待って HTML に反映される |
Nuxt などのフレームワークは、取得用の専用関数を用意しています。そちらを使う場合はフレームワークのドキュメントに従ってください(今回は未実行です)。
フックを登録できる場所と、警告が出る書き方
フックは setup の同期的な実行中に登録する必要があります。
// 警告になる例:async setup() の await の後
const C = defineComponent({
async setup() {
await sleep(1)
onMounted(() => console.log('呼ばれた')) // 登録されない
return () => h('i')
},
})
[Vue warn]: onMounted is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.
setTimeout のコールバックの中で登録した場合も、同じ警告が出てフックは呼ばれませんでした。
一方、<script setup> のトップレベルで await を使った場合は、await の後で登録しても呼ばれました(Suspense の中に置いて確認)。
<script setup>
import { onMounted } from 'vue'
onMounted(() => console.log('await の前に登録: 呼ばれる'))
await new Promise(r => setTimeout(r, 1))
onMounted(() => console.log('script setup のトップレベル await の後: 呼ばれる'))
</script>
<template><i /></template>
1. await の前に登録: 呼ばれる
2. script setup のトップレベル await の後: 呼ばれる
3. 警告: なし
| 登録する場所 | 結果(Vue 3.5.43) |
|---|---|
| setup / script setup の同期的な部分 | 登録される |
| setup から同期的に呼んだ関数(コンポーザブル)の中 | 登録される |
| script setup のトップレベル await の後 | 登録された |
| setup() 関数を async にして await の後 | 警告が出て登録されない |
| setTimeout・イベントハンドラの中 | 警告が出て登録されない |
どの書き方でも通用するように、フックの登録は最初の await より前に置くと決めておくのがおすすめです。
再描画の原因を調べるonRenderTrackedとonRenderTriggered
開発時だけ使える調査用のフックです。
import { defineComponent, h, ref, onRenderTracked, onRenderTriggered } from 'vue'
const C = defineComponent({
setup() {
const n = ref(0)
onRenderTracked((e) => console.log(`tracked: type=${e.type} key=${String(e.key)}`))
onRenderTriggered((e) =>
console.log(`triggered: type=${e.type} key=${String(e.key)} ${e.oldValue} -> ${e.newValue}`))
return { n }
},
render() { return h('i', this.n) },
})
マウントしてから n を 1 増やした結果:
1. tracked: type=get key=value
2. triggered: type=set key=value 0 -> 1
3. tracked: type=get key=value
「思ったより何度も再描画される」ときに onRenderTriggered を仕込むと、どの値の変更が再描画を引き起こしたかを旧値・新値つきで確認できます。
やりたいことから選ぶフックの逆引き表
| やりたいこと | 使うフック・API |
|---|---|
| 画面を開いたときにデータを取得する | onMounted(または setup、immediate つきの watch) |
| DOM 要素の大きさや位置を読む | onMounted |
| 外部ライブラリを要素に取り付ける | onMounted で生成、onBeforeUnmount か onUnmounted で破棄 |
| リスナー・タイマーを解除する | onUnmounted |
| 値を変えた直後に更新後の DOM を読む | await nextTick() |
| 特定の値が変わったときに処理する | watch |
| 更新のたびにスクロール位置を調整する | onUpdated(または flush: 'post' の watch) |
| タブ切り替えで戻ってきたときに取り直す | onActivated |
| 表示されていない間はポーリングを止める | onDeactivated |
| 子のエラーで代わりの表示に切り替える | onErrorCaptured |
| SSR でデータを取得してから HTML を作る | onServerPrefetch |
| 再描画の原因を調べる | onRenderTriggered |
よくある質問
Q. onMountedは親と子のどちらが先に呼ばれますか? A. 子が先です。3階層で確かめると、孫 → 子 → 親の順でした。setup と onBeforeMount は親 → 子 → 孫の順なので、「準備は外側から、完了は内側から」と覚えると整理しやすくなります。
Q. createdに当たるフックはComposition APIにありますか? A. 専用のフックはなく、setup(script setup のトップレベル)に直接書きます。Options API と併用して確かめると、setup は beforeCreate よりも前に実行されていました。
Q. onUnmountedで解除しないとどうなりますか? A. コンポーネントが画面から消えたあとも、window に登録したリスナーやタイマーは動き続けます。今回の型では、アンマウント後にリスナーとタイマーの数が 0 に戻ることを確認しました。登録と解除を同じコンポーザブルにまとめると、書き忘れを防げます。
Q. KeepAliveの中でonMountedは毎回呼ばれますか? A. 呼ばれるのは最初の1回だけです。キャッシュから戻るたびに呼ばれるのは onActivated で、初回の表示でも onMounted の後に呼ばれました。表示のたびに行いたい処理は onActivated に書きます。
まとめ
- Composition API のフックは12種。beforeCreate と created に当たる処理は setup に直接書く
- before 系は親 → 子 → 孫、完了系は孫 → 子 → 親の順。親の onMounted の時点で子孫の DOM はそろっている
- 更新フックは、自分の描画に関わる値が変わったコンポーネントだけで呼ばれる
- v-if の切り替えは子のアンマウントとマウント。KeepAlive の中では onDeactivated と onActivated に変わる
- onMounted で始めたものは onUnmounted で解除する。リスナーは同じ関数を渡す
- サーバーで呼ばれるのは setup・onServerPrefetch・onErrorCaptured。ブラウザ専用の処理は onMounted へ
- フックの登録は最初の await より前に置く
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Vueライフサイクル一覧|onMounted・onUnmountedの順番」
株式会社RJCでは、Vueのライフサイクルの仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。