はじめに
Vue.js におけるライフサイクルについて こちら でみた。
ただ 公式のこちら には上記の記事で扱った以外のフックとして以下の 3 つが取り上げられている。
- activated
- deactivated
- errorCaptured
本記事ではこれら 3 つのフックについて動きを見ていく。
お詫び
記事中、コンポーネントの名称について スクリーンショット と 文中の単語について表記ゆれがあります( Life Cycle
, LifeCycle
, Lifecycle
等 )が、同じコンポーネントを指しております。
お見苦しくて申し訳ありません。
前提
- Vue CLI でプロジェクトを作成していること
- 単一ファイルコンポーネントであること( 1 )
- 動作確認は
$ npm run serve
で起動した環境で行なっている -
activated
とdeactivated
の説明のために動的コンポーネントを扱っているが、動的コンポーネントそのものについては触れない
環境
Version | 備考 | |
---|---|---|
Vue | 2.6.11 | |
Vue CLI | 4.1.1 |
今回扱うライフサイクルフック
フック | タイミング | 備考 | |
---|---|---|---|
1 | activated | コンポーネントが活性化するとき | 同上 |
2 | deactivated | コンポーネントが非活性になるとき | 同上 |
3 | errorCaptured | エラーが発生したとき | 子孫コンポーネントからのエラーをキャッチする エラー伝播ルール を把握しておくこと |
activate と deactivate
前掲の公式ページによると、 activate
と deactivate
には次の 2 つが参照として挙がっている。
まず 組み込みコンポーネント の方を覗いてみると、次の説明がある。
[公式ページから引用]
動的コンポーネント周りでラップされるとき、
<keep-alive>
はそれらを破棄しないで非アクティブなコンポーネントのインスタンスをキャッシュします。<trasition>
に似ていて、<keep-alive>
はそれ自身 DOM 要素で描画されない抽象型コンポーネントです。activated
とdeactivated
ライフサイクルフックはそれに応じて呼び出されます。コンポーネントが
<keep-alive>
内部でトグルされるとき、activated
とdeactivated
ライフサイクルフックはそれに応じて呼び出されます。2.2.0 以降では、
<keep-alive>
ツリーの中の全てのネストされたコンポーネントに対して、activated
およびdeactivated
を発行します。主に、コンポーネント状態を保存したり、再描画を避けるために使用されます。
ということで、
-
activate
とdeactivate
はライフサイクルの仲間であり -
<keep-alive>
でコンポーネントを囲むことで、- 囲まれたコンポーネントは コンポーネントの切り替え が行われると
activate
とdeactivate
のフックが走る - このとき切り替えられたコンポーネントの Vue インスタンスは 破棄されずに残る
- 囲まれたコンポーネントは コンポーネントの切り替え が行われると
ことが分かる。
動的コンポーネント については、ここで取り上げると長くなるので割愛させていただく。前掲の公式ページ( 動的コンポーネント - keep-alive )を参照されたい。
では実際の動きをコードで見てみる。
確認用コードについて
- 動的コンポーネントの動きとあわせて見るため、親コンポーネントと 2 つの子コンポーネント、計 3 つのコンポーネントを用意した
- なお子コンポーネントについては、ログ出力の内容やコンポーネント名を除き、同じ構造である
- これは両者とも同じ構造の方が動作確認時に比較しやすいとの意図のもと、そうしている( 冗長ではあるがご容赦いただきたい )
親コンポーネント( LifeCycle2 )
<template>
<div :class="$style.lifecycle2">
<div :class="$style.wrapchild">
<input
id="Lifecycle21"
v-model="selectedComponent"
type="radio"
value="Lifecycle21">
<label for="Lifecycle21">Lifecycle21</label>
</div>
<div :class="$style.wrapchild">
<input
id="Lifecycle22"
v-model="selectedComponent"
type="radio"
value="Lifecycle22">
<label for="Lifecycle22">Lifecycle22</label>
</div>
<h1>selected component is ... {{ selectedComponent }}.</h1>
<keep-alive>
<component
:is="selectedComponent"
:class="$style.lifecycle2_child_area"/>
</keep-alive>
</div>
</template>
<script>
import Lifecycle21 from '@/components/LifeCycle2-1.vue'
import Lifecycle22 from '@/components/LifeCycle2-2.vue'
export default {
name: 'Lifecycle2',
components: {
Lifecycle21,
Lifecycle22
},
data: function() {
return {
selectedComponent: '',
}
},
beforeUpdate: function() {
console.log('[LifeCycle2] beforeUpdate.')
},
updated: function() {
console.log('[LifeCycle2] updated.')
},
beforeDestroy: function() {
console.log('[LifeCycle2] beforeDestroy.')
},
destroyed: function() {
console.log('[LifeCycle2] destroyed.')
},
errorCaptured: function() {
console.log('[LifeCycle2] errorCaptured.')
},
}
</script>
// スタイルは割愛
子コンポーネント1( LifeCycle21 )
<template>
<div :class="$style.lifecycle2_child">
<p>input on LifeCycle21.</p>
<input
:class="$style.message"
v-model="properties.message"
placeholder="edit me">
<p>Message is: {{ properties.message }}</p>
</div>
</template>
<script>
export default {
name: 'LifeCycle21',
data: function() {
return {
properties: {
message: 'default value.',
},
}
},
beforeDestroy: function() {
console.log('[LifeCycle2-1] beforeDestroy.')
},
destroyed: function() {
console.log('[LifeCycle2-1] destroyed.')
},
activated: function() {
console.log('[LifeCycle2-1] activated.')
},
deactivated: function() {
console.log('[LifeCycle2-1] deactivated.')
},
}
</script>
// スタイルは割愛
子コンポーネント2( LifeCycle22 )
<template>
<div :class="$style.lifecycle2_child">
<p>input on LifeCycle22.</p>
<input
:class="$style.message"
v-model="properties.message"
placeholder="edit me">
<p>Message is: {{ properties.message }}</p>
</div>
</template>
<script>
export default {
name: 'LifeCycle22',
data: function() {
return {
properties: {
message: 'default value.',
},
}
},
beforeDestroy: function() {
console.log('[LifeCycle2-2] beforeDestroy.')
},
destroyed: function() {
console.log('[LifeCycle2-2] destroyed.')
},
activated: function() {
console.log('[LifeCycle2-2] activated.')
},
deactivated: function() {
console.log('[LifeCycle2-2] deactivated.')
},
}
</script>
// スタイルは割愛
動作確認
-
初期表示( LifeCycle2 を表示 )
-
LifeCycle21 を選択
-
LifeCycle21 でデータ入力
-
LifeCycle22 を選択
-
LifeCycle21 を再度選択
-
画面上部のメニューから別ページを選択
-
LifeCycle2 に戻る >> LifeCycle21 を選択
動的コンポーネントを <keep-alive>
で囲まなかったら
上記は <keep-alive>
で動的コンポーネントを囲ったときの動きで、期待通り activate
と deactivate
が走ることが確認できた。
では動的コンポーネントを <keep-alive>
で囲まなかったときにどう動くのかを見ておきたい。
なお本項では親コンポーネントの <template>
をいじるだけで、子コンポーネントの変更はないので、子コンポーネントのコードは割愛する。
親コンポーネント( LifeCycle2 )
動的コンポーネントを <keep-alive>
で囲まなかったときにどう動くのか ?
<template>
<div :class="$style.lifecycle2">
<-- ******** -->
<-- 差分は割愛 -->
<-- ******** -->
<component
:is="selectedComponent"
:class="$style.lifecycle2_child_area"/>
</div>
</template>
// テンプレートの差分だけなのでスクリプトは割愛
// スタイルは割愛
動作確認
-
初期表示( LifeCycle2 を表示 )
-
LifeCycle21 を選択 >> LifeCycle21 でデータ入力
-
LifeCycle22 を選択
-
LifeCycle21 を再選択
ということで、
- 動的コンポーネントを
<keep-alive>
で囲まないと、コンポーネントの切り替えが行われたときに コンポーネントの破棄と生成 が行われること
が確認できた。
逆説的にいうと、
- 動的コンポーネントを
<keep-alive>
で囲むことでactivate
とdeactivate
がフックされること - またそうすることで、動的コンポーネントは別コンポーネントに切り替わってもインスタンスは非活性になるだけで生き続けること
- 再度もとのコンポーネントに切り替わることで、非活性 >> 活性に戻ること
と言うことができる。
errorCaptured
errorCaptured
についても、公式ページから説明を引用させていただく。
任意の子孫コンポーネントからエラーが捕捉されるときに呼び出されます。フックは、エラー、エラーをトリガするコンポーネントインスタンス、そしてどこでエラーが捕捉されたかの文字列情報、これら 3 つの引数を受け取ります。フックはエラーがさらにもっと伝播するのを防ぐために、false を返すことができます。
ということで、前傾のコードをベースにしつつ、こちらも動きを見ていく。
親コンポーネント( LifeCycle21 )
//
// スクリプトの差分だけなのでテンプレートは割愛
//
<script>
export default {
name: 'LifeCycle2',
//
// 差分のみ抜粋
//
errorCaptured: function(error, component, info) {
console.log('[LifeCycle2] errorCaptured.')
console.log(error)
console.log(component)
console.log(info)
// エラーの伝播を防ぐために、`false` を返す
// つまり更に上の親へエラーをあげることはせずに、ここで止める
return false
},
}
</script>
// スタイルは割愛
子コンポーネント( LifeCycle21 )
どちらか片方のコンポーネントでエラーを発生させれば、errorCaptured
のフックは確認できるので、今回は LifeCycle21
の方でエラーを発生させた。
//
// スクリプトの差分だけなのでテンプレートは割愛
//
<script>
export default {
name: 'LifeCycle21',
//
// 差分のみ抜粋
//
beforeCreate: function() {
// わざと例外を発生させて、
// 親コンポーネントである LifeCycle2.vue の `errorCaptured()` のフックに引っ掛けたい
this.properties.message = 'set on beforeCreate.'
},
}
</script>
// スタイルは割愛
動作確認
-
初期表示( LifeCycle2 を表示 )
-
-
LifeCycle21
を選択したことで、当該コンポーネントのインスタンスが動的に生成された - そのタイミングで子コンポーネントである
LifeCycle21P
でbeforeCreate
のフックが走り、例外が発生する - 発生した例外をフックして、親コンポーネントである
LifeCycle2
でerrorCaptured
が走った
-
errorCaptured
は 3 つの引数から以下の情報を得ることができる。
- 例外情報
- 例外が発生したコンポーネント情報
- 例外が発生し場所に関する情報を含む文字列
上記を踏まえ、改めてログを見てみると、先に上げた例外発生時の動きと errorCaptured
の引数から得た情報が合致していることがわかる。
まとめ
activate と deactivate
-
子コンポーネントを動的コンポーネントとして扱い、
<keep-alive>
で囲むことでactivate
とdeactivate
のフックが実行される -
<keep-alive>
で囲まれた子コンポーネントは、コンポーネントの切り替えが発生してもインスタンスは **非活性になるだけ **で 破棄されずに生き残る -
でも 親コンポーネントごと別のコンポーネントに切り替えられたら
<keep-alive>
で囲んでいても子コンポーネントのインスタンスは破棄される -
動的コンポーネントを
<keep-alive>
で囲まなかったら、通常のインスタンスと同じで コンポーネントの切り替え で インスタンスの生成と破棄 が行われる
errorCaptured
- 子コンポーネントでエラー が発生したら 親コンポーネント側 のこのフックが走る
-
errorCaptured
は 3 つの引数から以下の情報を得ることができる。- 例外情報
- 例外が発生したコンポーネント情報
- 例外が発生し場所に関する情報を含む文字列
-
false
で復帰することでエラーがさらにもっと伝播するのを防ぐことができる
ソースコード
今回の記事で動作確認に使用したコードは下記にアップしております。
ご参考まで。( 以下は ブランチのリンクですが、 master にもマージ済みです )
参考
公式
-
Vue.js のコンポーネントを単独のファイルとして作成する機能
拡張子「.vue」のファイルのことで<template>
,<script>
,<style>
のブロックで構成されている。 ↩