(以前社内ブログで挙げていた記事の再掲です)
今回取り上げるのは、Vue2とNuxt2です。
Vueにはライフサイクルという機能があります。
ライフサイクルフックというのは、画面の見た目が出来上がるまでをタイミングをいくつかに分けて、そのタイミングに合わせて処理を行わせることができるというものです。
そのため、画面が出来上がるまでに処理されていく順番とタイミングを覚えておけば、
「データ取ってきて画面に反映される前にデータを加工しときたいんだよな・・」みたいなことが簡単に実現できます。
今回はVueのライフサイクルフックに加えて、Nuxtだとさらに細かい点で処理を挟めるらしいので、
それをまとめてみました。
Vueのライフサイクルはどんな感じなの
Vueがどのようにライフサイクルされていくかを書いていきます。
Vueのライフサイクルフックは一つずつ見ていくより
一連の流れでタイミングを理解したほうがスッと入ってきます。
図だけじゃさっぱりかもしれませんが、こんな感じです。
ここで書いてある、フックの箇所を指定して処理を書けば、欲しいタイミングで欲しい処理をしてもらうことができます。
この図について、改めて説明します。
わからなくなったら図を見ながら読み返してもらうといいと思います。
まずVueファイルは最初、New Vue()
されます。
サイトにアクセスして画面が表示されていくのをイメージしてみてください。
そのタイミングです。
この直後が、beforeCreate()
です。
beforeCreateのタイミングでは、Vueがインスタンスとして生成され始めていますが、
内部のデータがまだ初期化される前なので、データにはアクセスできません。
次にVueのインスタンスの生成が完了し、内部のデータも初期化が終わります。
ここがcreated()
です。
created
からは内部のデータが作り終わってますので、this.〇〇みたいなのが使えるようになっています。
次にVueは、書かれているコードをもとに仮想DOMを作ります。
(イメージとしては、実際に画面に反映する見た目を、Vueが仮で同じものを先に作ってみる感じです。)
そしてそれを本物のDOMに対して適用しにいきます。この適用しにいくことをマウントとみんな呼んでます。
このマウントが行われる前のタイミングが、beforeMount()
、マウントが終わるとmounted()
になります。
マウントが終わった段階だと画面のレンダリングも完了しているため、DOMのエレメントも直接呼び出すことができます。
ここで画面がしっかりと表示されてる段階です。
mountedで画面が完成したので一旦一区切りですが、なんらかの処理があり、 Vueが使っているデータが書き換えられたりすると思います。
その場合は、更新されたことをVueが勝手に感知して、書き換えられたデータをもとにDOMを改めて更新してくれます。
このDOM更新前がbeforeUpdate()
、書き換え後がupdated()
です。
注意点としては、beforeUpdateのタイミングであっても、内部の値は更新されていることです。
あくまでもDOMへの反映前か後かの差ですので、書き換えられる前の値は取ってこれないので注意です。
それと図では、updatedの後にmountedへ戻っているように見えますが、mountedは呼ばれないので、これも注意してください。
そして最後に、この生成されたVueのインスタンスが消滅するタイミングもあると思います。遷移だったり、強制終了だったり・・・
そのインスタンスが消える直前のことをbeforeDestroy()
、消えたタイミングがdestoryed()
です。
ここまで長い日本語を読んでくれた後に改めて図を見るとなんとなくわかるかなと思います。
Vueのライフサイクルフックはこんな感じです。
一応公式も載せておきます。
じゃあNuxtのライフサイクルフックってどんなの
Vueのライフサイクルフックをベースに、Nuxtはフックをかけられる箇所がぐっと増えます。
少しややこしいのが、サーバサイドのみのライフサイクルとクライアントサイドのライフサイクルが出てくるところです。
まずなにが増えるかと言うと、
サーバのみのライフサイクルがこれ
- serverMiddleware
- nuxtServerInit
サーバーとクライアント両方での実行がこれ
- RouteMiddleware
- Validate
- asyncData
- fetch
これらが、どんなタイミングで使えて、何ができるのかは後で解説するとして、
一旦Vueのライフサイクルフックと足します。
結構増えましたね・・
早速どんな時に使うのかまとめていきます。
一応公式です。全部載ってます。
https://nuxtjs.org/ja/docs/concepts/nuxt-lifecycle
Nuxtのライフサイクルフックの使われ方
では、新しく増えたものを一つずつ見ていきます。
serverMiddleware
サーバサイドのみで動くフックです。
APIサーバを書いたりします。
使い方としては、処理を書いたファイルをnuxt.config.js
ファイルのserverMiddlewareに登録すれば使えます。
詳細は公式を見てください。私は使ったことないです・・・
https://nuxtjs.org/ja/docs/configuration-glossary/configuration-servermiddleware/
nuxtServerInit
これもサーバのみのフックです。
あらかじめ初期値を渡す操作を書いたり、ログイン認証したりするコードを書けます。
Cookieからデータを取り出したりも可能です。
クライアントに処理を渡す前にサーバでやっておくべき処理を書いちゃいます。
使い方はstore/index.jsにnuxtServerInitアクションを追加するだけです。
公式のサンプルは以下
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)}
}
}
}
RouteMiddleware
これは少しややこしいですが、サーバでも、クライアントでも実行されます。
サーバーで1回、クライアントではページ遷移のたびに呼ばれます。
主にルーティング処理などを書きます。
例えば、認証していないユーザーはログインページにリダイレクトされるなど。
使い方はmiddlewareディレクトリの中にファイルを作成し、nuxt.config.js
のmiddleware
に登録することで処理を反映できます。
公式の例はこんな感じです。
export default *function* (context) {
// userAgentプロパティをコンテキストに追加します
context.userAgent = process.server
? context.req.headers['user-agent']
: navigator.userAgent
}
export default {
// nuxt.config.jsでの登録
router: {
middleware: 'sample'
}
}
validate
これもサーバーで1回、クライアントではページ遷移のたびに呼ばれます。
結構シンプルな使い勝手で、使いたいコンポーネント内で、validateメソッドを追加すればOKです。
このvalidateでエラーが出たときは、自動的にエラーページが表示されます。
公式の例です。
export default {
validate({ params }) {
// 数字でなければなりません。違ったら自動的にエラーページに遷移。
return /^\d+$/.test(params.id)
}
}
https://nuxtjs.org/ja/docs/components-glossary/validate/
asyncData
APIなどからデータを取得し、ローカルデータを格納する時に使われます。
必要なデータをAPI叩いて取得していきます。
asyncDataは引数にcontext(コンポーネントのthis自体の情報のこと)を取って、そのデータを利用したりしなかったりします。
内容はこの後出てくるfetchと似ていますが、できることが少し違います。
fetchのところでまとめます。しばしお待ちを。
使い方としては、使いたいコンポーネント内でasyncDataメソッドを追加します。
公式の例です。
async asyncData({ $http }) {
const mountains = await $http.$get('https://api.nuxtjs.dev/mountains')
return { mountains }
}
https://nuxtjs.org/ja/docs/features/data-fetching/#async-data
fetch
APIなどからデータを取得する時に使われます。
画面に必要なデータをとりあえずAPI叩いて、色々持ってこさせるタイミングですね。
よく議論になるのが、asyncDataとfetch、一緒やんけ!ですが、
ちょっと違います。
asyncDataとfetchの大きな違いは呼ばれるタイミングです。
asyncData
→ beforeCreate
→ created
→ fetch
という順です。
createdのタイミングがポイントで、created以降からは内部のローカルデータにアクセスできますので、
asyncData
ではthis.○○のアクセスができず、
fetch
だとthis.○○が使えるということです。
じゃあasyncData
はどうやって取ってきたデータをローカルに置いておくかというと、
取ってきた後、ローカルデータにマージして残しています。
簡単に言うと、asyncDataは処理内で変数を作ってそれがコンポーネント内で定義されますが、
fetchはあらかじめ変数を定義しておいて、そこにthis.〇〇でアクセスするイメージです。
よくわからないと思うので比較のソースを見たほうが早いかもです。
asyncDataの例
export default {
async asyncData(context) {
const data = await context.$axios.$get(
`https://jsonplaceholder.typicode.com/todos`
)
// todosはローカルデータに定義されていなくてOK。
return { todos: data.Item }
// todosはローカルにマージされます。
}
}
fetchの例
export default {
data() {
return {
todos: []
}
},
async fetch() {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
)
// todosはローカルデータに定義されている必要あり。
this.todos = data
}
}
あと、細かい話ですけど、
fetchはどのコンポーネントでも使えますが、
asyncDataはページコンポーネント(pagesディレクトリ配下の.vueファイル)でしか使えないので気をつけてください。
https://nuxtjs.org/ja/docs/components-glossary/fetch/
https://nuxtjs.org/ja/announcements/understanding-how-fetch-works-in-nuxt-2-12/#asyncdata-vs-fetch
まとめ
こんな感じで、一応ざっとVueとNuxtのライフサイクルフックを紹介しました。
まずはVueのライフサイクルフックのタイミングと、できることを覚える。
Vueのライフサイクルは流れの理解が大事です。
そしてNuxtは、Vueのライフサイクルフックをベースに乗っかってくるので、
補足知識のようにNuxt特有のライフサイクルフックをVueのライフサイクルに足してあげてください。
参考
https://qiita.com/chan_kaku/items/7f3233053b0e209ef355
https://nuxtjs.org/ja/docs/concepts/nuxt-lifecycle