Vueを使った実装を始めてもう2年ほど経ちますが、ライフサイクルフックの理解はまだ「なんとなく」で既存のコードをベースに雰囲気で書いてしまっていることがあります (反省)
特に初期フェーズから実装している案件は、コンポーネントが多階層になっていて「ここでデータを取得出来ているはずなのに、このコンポーネントに表示されない」みたいなライフサイクル的な問題がたまに発生します。
単純なライフサイクルフックを示した図はネットにゴロゴロ落ちていますが、親子コンポーネント間のライフサイクルに関する分かりやすい記事があまりなく、自分で確かめてみようと思った次第です。
検証
今回は案件でよく利用するcomputed
, created
, mounted
を検証します。
単一コンポーネント
<template> <div>{{ test }}</div> </template> <script> export default { created() { this.$logger.info('created') }, mounted() { this.$logger.info('mounted') }, computed: { test() { this.$logger.info('computed') }, }, } </script>
結果
[info] created [info] computed [info] mounted
ライフサイクルダイアグラムに記述が無い computed
は mounted
の前に発火するようです。
親子コンポーネント間
親コンポーネント
<template> <div> <div>{{ test }}</div> <Child /> </div> </template> <script> export default { created() { this.$logger.info('parent created') }, mounted() { this.$logger.info('parent mounted') }, computed: { test() { this.$logger.info('parent computed') }, }, } </script>
子コンポーネント
<template> <div>{{ test }}</div> </template> <script> export default { created() { this.$logger.info('child created') }, mounted() { this.$logger.info('child mounted') }, computed: { test() { this.$logger.info('child computed') }, }, } </script>
結果
[info] parent created [info] parent computed [info] child created [info] child computed [info] child mounted [info] parent mounted
親のmounted
が、子供よりも後に発火される挙動になっていました。
<template> <div> <div>{{ test }}</div> <Child :greeting="greeting" /> </div> </template> <script> export default { data() { return { greeting: '', }, created() { this.$logger.info('parent created') }, mounted() { this.$logger.info('parent mounted') this.greeting = 'hello!' }, computed: { test() { this.$logger.info('parent computed') }, }, } </script>
子コンポーネント
<template> <div>{{ test }}</div> </template> <script> export default { props: { greeting: { type: String, required: true, }, }, created() { this.$logger.info('child created data:', this.greeting) }, mounted() { this.$logger.info('child mounted data:', this.greeting) }, computed: { test() { this.$logger.info('child computed data:', this.greeting) }, }, } </script>
結果
[info] parent created [info] parent computed [info] child created data: [info] child computed data: [info] child mounted data: [info] parent mounted [info] child created data: hello!
created
は関数内で利用されている値に変化が応じた時に発火します。
初回created
発火時にはデータが入っていませんが、親のmounted
で得たデータを検知して再度発火しhello!
を取得出来ています。
改修の際に詰まったんですが、親のmounted
でストアに格納したデータを子コンポーネントで取得できなかった理由もこれでした。
created
はデータの変化を検知して発火するとなると、似ている処理を行うwatch
とどちらの方が早いのか…ですが、
子コンポーネント
<template> <div>{{ test }}</div> </template> <script> export default { props: { greeting: { type: String, required: true, }, }, computed: { test() { this.$logger.info('child computed data:', this.greeting) }, }, watch: { greeting: { handler() { this.$logger.info('child watch data:', this.greeting) }, }, }, } </script>
結果
[info] child watch data: hello! [info] child created data: hello!
watch
の方が早かったです。
よって親子コンポーネント間のライフサイクルフックの順序は下記の様になります
① 親のcreated
② 親のcomputed
③ 子のcreated
④ 子のcomputed
⑤ 子のmounted
⑥ 親のmounted
以上です!