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

ライフサイクルダイアグラムに記述が無い computedmounted の前に発火するようです。

 

親子コンポーネント間

親コンポーネント

<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

以上です!