改めてまとめてみた
気になったのでライフサイクルフックとナビゲーションガードの発火順番をまとめて、個人的使い分けを書いてみました。
個人的使い分けの方はポエムみたいなもんですので忘れてください。
ご意見大歓迎です!
ナビゲーションガードはコンポーネント内のガードに絞って検証しましたが、グローバルなナビゲーションガード含めた発火順もこちらを見ればわかるかと思います。
ちなみにこの記事は、「Vue Advent Calendar 2020」の11日目の記事です。(人生初アドベントカレンダー!)
ライフサイクルフックとナビゲーションガードとは
ライフサイクルフック
created,mountedなど(この辺しか使ったことないな…)
ざっくり言うとコンポーネントの生成、更新、破棄、もしくはそれらの前後に呼ばれるメソッド群のこと。
ナビゲーションガード
beforeRouteEnter,beforeRouteLeaveなど
ざっくり言うとURL(ルート)の確立、破棄の前後に呼ばれるメソッド群のこと。
そもそも発火する順番は?
console.log()しまくって調べました。
beforeRouteEnter
beforeCreate
created
beforeMount
beforeRouteEnter内のnextのコールバック
mounted
beforeUpdate
updated
beforeRouteLeave
(beforeRouteUpdate)
beforeDestroy
destroyed
beforeRouteUpdateはコンポーネントがルートが変わっても描画され続ける場合しか呼ばれない
beforeRouteEnterはbeforeRouteUpdateが呼ばれる場合は呼ばれない
詳しく見ていこう
thisにアクセスできるか調べる
できない以外はできる
beforeRouteEnter(できない)
beforeCreate(できない)
created
beforeMount
beforeRouteEnter内のnextのコールバック
mounted
beforeUpdate
updated
beforeRouteLeave
(beforeRouteUpdate)
beforeDestroy
destroyed(できない)
beforeRouteEnterは厳密にはthisにアクセスできないが、nextのコールバックにインスタンスを引数にもらえる関数が書けるのでまぁできると言えばできる。
beforeRouteEnterの中でも「thisにアクセスしたいからnextのコールバックに書く」か、「それ以外」で書くかで呼ばれるタイミングが違うのが罠っぽい挙動だなー
個人的に思うライフサイクルフックとナビゲーションガードの使い分け
前提として、「その処理がルートを意識するか」という観点があると思う。
createdとbeforeRouteUpdate
例えばuser/{userId}
で使いまわされているUserコンポーネントがあるとする。
このコンポーネントに必要なデータ取得時において、
「コンポーネントが生成される度にAPIを叩いてそのコンポーネントに必要な最新のデータを取得したいからライフサイクルフック、createdを使う」
と、暗に考えがちだがこの場合ルートが変わってもUserコンポーネントが使い回されるためcreatedが発火しないので古いデータが使われ続ける。
例として、user/1
からuser/2
にルートが変わった時、user/1
とuser/2
に共通なUserコンポーネントのcreatedは呼ばれないので、user/2
に遷移後もuser/1
のデータでUserコンポーネントは描画され続けてしまう。
では「もっと後のmountedとかに書けばいいのでは?」と思う人はさすがに少ないと思うが、描画され続ける場合はmountedも発火せず、発火したとしてもupdateだし、updateは初回で発火しないのでダメ。
というかこの「どこで発火するかをコンポーネントのライフサイクルで考える」のが不毛だ。
今回の例だと明らかにデータの更新がuser/{userId}
というuserId、つまりルートに依存しているのだからその処理はナビゲーションガードに書くべきだ。
ではどのナビゲーションガードを選ぶか
今回の例では、もう公式に答えが書いてある。
beforeRouteUpdateだ。
beforeRouteLeaveとbeforeDestroy
たとえば共通で使いまわしているLoadingコンポーネントがある。
こいつはApp.vue的な上位のコンポーネント(レイアウトコンポーネント)からのみ呼び出されていて、ページコンポーネントからemitすることで表示、非表示を切り替えているとする。
|-App(Layout)---|
| Loading |
| |
| |-Page------| |
| | | |
| | | |
| |-----------| |
|---------------|
そうするとどういうことが起こるか。
ローディング中にページを切り替えるとローディングが残ってしまうのである!
これを防ぐにはページが切り替える前にemitして非表示にしてからページ遷移するようにしたい。
さてどうするか。
もちろんこれもルートを意識した処理と言える。
パスパラメータに依存した処理ではないものの、ページ遷移によってフックされるからだ。
「そんなこと言ったらコンポーネントのライフサイクルフックだってページ遷移によってフックされるぞ!」
という声が聞こえてきそうだけど、そっちはページ遷移によってコンポーネントの破棄、生成が行われるからフックされるのである。
だからされない場合もあるでしょ?ほら、一個上の「createdとbeforeRouteEnter」を見てみて。
逆にページ遷移以外でもコンポーネントのライフサイクルフックは発火する。
v-ifがfalseになった時などである。
この辺が雰囲気でやっていると痛い目に遭う時がある。
(ここだけちょっとウザい文体になってしまったな…)
少し話が逸れてしまったが、PageコンポーネントのbeforeDestroyでLoadindコンポーネントの表示、非表示を切り替えたとする。
これもPageコンポーネントが「createdとbeforeRouteEnter」の時のUserコンポーネントと同じく、パスパラメータでデータ取得し直すような描画され続けるコンポーネントだった場合beforeDestroyが発火せず、この問題は解決されない。
ので、やっぱりナビゲーションガードが適切。
ではどのナビゲーションガードを選ぶか
ルートを意識した処理なのでこいつはナビゲーションガードに書くとして、ページ離脱時にLoadingをキャンセルしたいので使うのはbeforeRouteLeaveである。
ここならまだページインスタンス(this)にアクセスできるし、インスタンスのライフサイクルに惑わされず間違いなくページ離脱時にフックできる。
いやほぼナビゲーションガードに書くのが良さげじゃん!
と思った方もいるかもしれないが、ここでは敢えてそういう例を上げただけであって実際はほぼほぼライフサイクルフックに書くことが多いのではないだろうか。
とにかく罠にハマりやすいのはuser/{userId}
で使われるページコンポーネントのようなページが変わっても使いまわされるコンポーネントのフックなのでそこだけ気をつければ大丈夫だろう。
「ルート変更に伴う処理ならルートナビゲーションを、それ以外はライフサイクルフックを」と覚えておけば大丈夫そうです。
いや、長いだけで内容薄いなこの記事。。
公開するのやめようかな…(もったいないので公開しました)
良い年末年始を!