vue.jsを含めフロントエンドの技術を学び始めるとライフサイクルという言葉を目にする機会が増えてきます。私がそうであったようにvue.jsを含めJavaScriptのフレームワークに触れたことがない人にとってはライフサイクルというものが一体何なのか理解するのが最初は非常に難しいと思います。最初はライフサイクルの中で行われているすべての処理を理解しようとせず利用頻度の高いcreated, mountedで何が行われるのかということと2つの区別がはっきりつけばライフサイクルの理解が進みます。本文書ではvue.jsのライフライクルフックcreatedとmountedの違いを要素elを使って説明していきます。要素elの説明は後ほど出てくるので安心してください。さらにVue3のOptions APIとComposition APIを利用してcreated, mountedの動作確認を行っています。

利用しているvue.jsのバージョンは2、3です。公開当時は2しかなかったので3を追記する形になっています。vue.jsはcdnを利用して使うため特別な環境の構築は必要ありません。デスクトップ等にindex.htmlファイルを作成し一緒に動作確認を行なってみてください。2と3を交互に動作確認するのでindex_v2.html, index_v3.htmlのように2つのファイルを準備したほうが楽です。
fukidashi

下記はvue.jsの公式サイトに掲載されているライフサイクル図です。今回はこの図の中でelとライフサイクルフックcreated, mountedさらにbeforeCreateの3つのみ注目します。

vue.jsライフサイクル
vue.jsライフサイクル

ライフサイクルフックとは

created, mountedはライフサイクルフックと呼ばれ、vue.jsの初期化の中の決められたタイミングで実行される関数です。各ライフサイクルフックの中にプログラムを記述することでvue.jsの初期化の流れの中で記述したプログラムを必ず実行させることができます。

初期化とはvue.jsが使えるようになるまでのvue.jsの内部で行われる処理のことです。WindowsやMac OSが起動するときに接続しているデバイスを認識したらサービスを起動したりするためにユーザが利用できるまで少し時間がかかります。Vue.jsでも同様にユーザが利用できるまでに内部でさまざまな初期化の処理が行われます。
fukidashi

APIなどを利用して外部からデータを取得してブラウザに表示させたい場合は、ライフサイクルフックの中で外部リソースからデータ取得を行うことができるaxiosライブラリ、fetch関数を利用してデータ取得のプログラムを記述しておきます。ライフサイクルフックの中にデータ取得のプログラムを記述することでvue.jsの初期化中にデータの取得を開始し取得が完了するとブラウザにそのデータ内容を表示させることができます。

バージョンの確認

動作確認を行う前に現在利用しているvue.jsのバージョンを確認したい場合にはVue.versionで確認することができます。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <body>
      <div id="app"></div>

      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      <script>
        console.log(Vue.version);
        //2.7.10
      </script>
    </body>
  </body>
</html>

npmでインストールを行っている場合はpacakage.jsonファイルに記述されています。


{
//略
  "dependencies": {
    "vue": "^3.2.37"
  },
  "devDependencies": {
//略
  }
}

動作確認を行う際にVue2とVue3では異なるcdnを利用しています。cdnが利用できない場合もあるのでその時はVueの各バージョンのドキュメントを確認してください。Vue2はhttps://v2.vuejs.org/ , Vue3はhttps://vuejs.org/です。


//Vue2
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js></script>

//Vue3
<script src="https://unpkg.com/vue@3"></script>

elがなにか確認してみよう

Vue.jsのバージョン2を利用している場合

ライフサイクルを記述した上のライフサイクル図の中に”el”という文字列を見つけることができます。elはvue.jsインスタンスがマウントを行う要素で、elを指定した要素の中でだけvue.jsを動かすことができます。Vueをインスタンス化する際にnew Vueで下記のようにしてelプロパティに#appといった要素を特定するidの値を記述します。

要素のidはappという名前である必要はなく任意の名前をつけることができます。idの値はdiv要素に対してdiv id=”app”で設定しています。Vueのコンポーネントがそのid=”app”に挿入されることをマウントと呼びます。コンポーネントがその場所からなくなることをアンマウントと呼びます。マウントでコンポーネントがブラウザに表示され、アンマウントでブラウザ上からコンポーネントが消えます。
fukidashi

var app = new Vue({
  el: '#app',
  data: {

  },
})

このelが一体どのような情報を持っているのか確認してみましょう。elにはthis.$elでアクセスすることができます。thisはvueのインスタンスを表します。

button要素を作ってv-onディレクティブでclickイベントを設定し、クリックが行われたらshowElメソッドを実行しコンソールにthis.$elの中身を表示させます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>LifeCycle Hooks</title>
  </head>
  <body>
    <div id="app">
      <h1>Hello World</h1>
      <button v-on:click="showEl">show el</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        el: '#app',
        data: {},
        methods: {
          showEl: function () {
            console.log(this.$el);
          },
        },
      });
    </script>
  </body>
</html>

デベロッパーツールのコンソールに<div id=”#app”></div>が表示されます。中身は、document.getElementById(‘app’)を使って取得できるものと同じです。

this.$elの中身を表示
this.$elの中身を表示

getElementById(‘app’)で取得できる要素と同じなので、showElメソッドのconsole.logの前に下記のコードを追加するとinnnerHTMLで中身を書き換えることも可能です。buttonをクリックするとHello WorldがHello Vueに書き換えられることが確認できます。


this.$el.innerHTML = "<h1>Hello Vue</h1>";

ここまでの動作確認でelはnew Vueインスタンスで指定した要素そのものであることがわかりました。

Vue.jsのバージョン3を利用している場合

本文書ではcdnにVue.jsのバージョン2を利用しています。Vue.jsのバージョン3ではVueインスタンスの作成方法が変更になっています。Vue.jsのバージョン3を利用した場合にthis.$elに何が表示されるのか確認します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>LifeCycle Hooks</title>
  </head>
  <body>
    <div id="app">
      <h1>Hello World</h1>
      <button v-on:click="showEl">show el</button>
    </div>

    <script src="https://unpkg.com/vue@3"></script>
    <script>
      Vue.createApp({
        methods: {
          showEl() {
            console.log(this.$el);
          },
        },
      }).mount('#app');
    </script>
  </body>
</html>

デベロッパーツールで確認するとappの要素ではなくtextと表示されます。表示される内容バージョン2, バージョン3では大きく異なっていることが確認できます。

vue 3でのthis.$elの中身
vue 3でのthis.$elの中身

createdとmountedの違い

Vue2の場合

elの中身がわかったので、ライフサイクルフックのcreatedとmountedの違いを確認しておきましょう。createdとmountedの違いは下記のように説明されているのをよく見かけます。

createdはDOMがまだ作られていない状態で、mountedではDOMが作成された直後の状態です。

DOMの作成、未作成でライフサイクルフックに違いがあるということなので、this.$elを使ってその違いを確認してみましょう。確認方法はシンプルでcreatedとmountedにconsole.logでthis.$elがそれぞれの状態でどのようになっているか確認するだけです。

this.$elはDOMの要素なのでDOMが作成されていないとthis.$elは存在しません。


new Vue({
  el: '#app',
  data: {
  },
  created : function(){
    console.log('created')
    console.log(this.$el)
  },
  mounted : function(){
    console.log('mounted')
    console.log(this.$el)
  }
})

createdではDOMが作成されていないので、this.$elはundefinedとなっており、mountedではDOMの作成が完成しているので<div id=”app”></div>が表示されます。

createdとmountedでのthis.$elの状態
createdとmountedでのthis.$elの状態
ライフサイクル図で記述されている通り、先にcreatedが実行され、その後mountedが実行されることも実際に動作確認を行うことで理解することができます。
fukidashi

これでcreated, mountedの違いがわかりました。そのため、createdの時にgetElementById(‘id’)を使ってDOMの要素を取得しようとしても取得することはできません。しかしmounted時にはDOMの作成が完了しているのでDOMの要素が取得できるためgetElementByIdだけではなくDOMに対してアクセスを行うvue.js以外のライブラリjQuery等もこの時点で使用可能となります。

Vue3の場合

Vue3の場合もcreated, mountedの違いをコンソールで確認しておきましょう。


Vue.createApp({
  methods: {
    showEl() {
      console.log(this.$el);
    },
  },
  created: function () {
    console.log('created');
    console.log(this.$el);
  },
  mounted: function () {
    console.log('mounted');
    console.log(this.$el);
  },
}).mount('#app');

Vue2では異なりcreated時のthis.$elはundefinedではなくnullになっていることが確認できます。しかし#textと表示されている通りmountedでは$elにアクセスできていることがわかります。

Vue3の場合
Vue3の場合

createdでは何が完了しているのか

Vue2の場合

ではcreatedでは何が完了しているのでしょう。先程まではthis.$elでelの状態を確認しましたが、その中身は見えませんでした。今度はthisを使ってVueインスタンスを確認します。今回はdataにmessageプロパティの設定も行っておきます。


new Vue({
  el: '#app',
  data: {
    message : 'Hello World'
  },
  created : function(){
    this.message = 'Hello Vue'
    console.log('created')
    console.log(this)
  },
});

createdではVueインスタンスの作成が完了しているので、設定しているmessageをVueの中で確認することができます。this.messageをcreatedの中で上書きした値も反映されていることが確認できます。これでcreatedでインスタンスの作成が完了、dataオブジェクトがリアクティブになっているという説明を受けても理解することができます。

ここでのリアクティブとは値の更新を行うと更新が反映されるという認識で読み進めてください。
fukidashi
createdの状態でVueインスタンスを確認
createdの状態でVueインスタンスを確認

dataオブジェクトがリアクティブになっているので下記のように設定を行うとブラウザ上には”Hello Vue”が表示されます。


<div id="app">
  <h1>{{message}}</h1>
</div>

dataオブジェクトがリアクティブになっているのでcreatedの中でAPIを使ってデータ取得を開始しても取得したデータを設定することができるのでcreatedの中で外部からデータを取得しても問題がないことがわかるかと思います。このことが理解できた上で下記のようなコードを見ても納得できるかと思います。createdの中でデータプロパティusersに外部サーバから取得したデータを挿入しています。


  created :function(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
  }
上記をそのまま記述してもaxiosが必要なのでエラーとなります。axiosを利用する場合はaxiosのcdnを追加する必要があります。
fukidashi

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>LifeCycle Hooks</title>
  </head>
  <body>
    <div id="app">
      <h1>Hello World</h1>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      new Vue({
        el: '#app',
        data: {
          users: [],
        },
        created: function () {
          axios
            .get('https://jsonplaceholder.typicode.com/users')
            .then((response) => (this.users = response.data));
        },
      });
    </script>
  </body>
</html>

axiosではなくfetch関数を利用した場合は以下のように記述することができます。


async created() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await response.json();
  console.log(users)
},

async, awaitをまだ学習していない人であれば下記のように記述も可能です。


created() {
  fetch('https://jsonplaceholder.typicode.com/users')
    .then((response) => response.json())
    .then((data) => console.log(data));
},

Vue 3の場合

Vue3の場合はcreatedの中でmessageプロパティの値が更新できるか確認しておきます。


Vue.createApp({
  data() {
    return {
      message: 'Hello World',
    };
  },
  methods: {
    showEl() {
      console.log(this.$el);
    },
  },
  created() {
    console.log('created');
    this.message = 'Hello Vue';
    console.log(this);
  },
}).mount('#app');

下記のようにcreatedで確認しているthisの表示は変わっていますがmessageを見るとHello WorldからHello Vueに更新されていることがわかります。

Vue3の場合のVueインスタンスの中身
Vue3の場合のVueインスタンスの中身

createdとbeforeCreateの違い

beforeCreateでは、dataプロパティの中身を確認することができますがcreatedのようにmessageの上書きを行うことはできません。dataプロパティに関係のない処理を行う際に利用することができます。


  beforeCreate : function(){
    
    this.message = 'Hello Vue'

    console.log('beforeCreate')
    console.log(this)
}
beforeCreateでdata変更

これでbeforeCreateでインスタンスの作成が完了、dataオブジェクトがリアクティブになっていないという説明を受けても理解することができます。

ここまでの説明を読み終えるとelの理解、beforeCreate, created, mountedの3つのライフサイクルフックの違いをしっかり理解できたかと思います。ライフサイクル図には他にもさまざまなライフサイクルフックが記述されていました。今回は3つですが、それぞれのフックで行われることが異なるので違いを調べるとよりVue.jsの理解が深まると思います。

Options API vs Composition API

Vue3にはこれまでの記述方法のOptions APIの他に新たにComposition APIで記述することができます。Composition APIではcreatedなど一部のライフサイクルフックがなくなっています。Composition APIでのmounted Hookの記述方法はなくなっているのでcreated Hookがどうなったかについて確認を行なっていきます。

Options APIでcreated, mounted

先程確認を行っていますが再度Options APIを利用してcreated, mountedの動作確認を行います。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>LifeCycle Hooks</title>
  </head>
  <body>
    <div id="app">
      <h1>Hello World</h1>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
      Vue.createApp({
        created() {
          console.log('created');
        },
        mounted() {
          console.log('mounted');
        },
      }).mount('#app');
    </script>
  </body>
</html>

デベロッパーツールのコンソールを確認するとcreated, mountedが表示されます。

Options APIでのcreated, mounted
Options APIでのcreated, mounted

createdの中でdataで定義したmessageプロパティが更新されることも確認しておきます。messageプロパティの初期値は’Hello World’ですが、createdの中で’Hello Vue’に上書きしています。createdの中でmessageプロパティにアクセスすることができるためブラウザ上には更新されたHello Vueが表示されます。


<template>
  <h1>{{ message }}</h1>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello World',
    };
  },
  created() {
    this.message = 'Hello Vue';
    console.log(this.message);
  },
};
</script>

Composition APIのmounted

Composition APIではmountedではなくonMountedに名前が変わっている上createdは無くなっています。Options APIとは記述方法が異なるのでComposition APIでonMountedの動作確認を行います。


Vue.createApp({
  setup() {
    Vue.onMounted(() => {
      console.log('mounted');
    });
  },
}).mount('#app');

デベロッパーツールにはmountedが表示されます。createdはなくなりましたがその代わりとなる方法はあるのでしょうか。Composition APIではcreated Hookはなくなりましたがsetup関数の中にそのまま処理を記述することでcrteatedと同じことができます。


Vue.createApp({
  setup() {
    console.log('created');
    Vue.onMounted(() => {
      console.log('mounted');
    });
  },
}).mount('#app');

コンソールにはcreatedが表示されました。createdが表示されるだけではcreatedと同じことができるかはわからないのでリアクティブなデータmessageをrefで定義して更新できるか確認します。


<body>
  <div id="app">
    <h1>{{ message }}</h1>
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script>
    Vue.createApp({
      setup() {
        const message = Vue.ref('Hello World');
        message.value = 'Hello Vue';
        return {
          message,
        };
      },
    }).mount('#app');
  </script>
</body>

動作確認を行うとブラウザにHello Vueが表示されることが確認できます。

setup関数の中でfetch関数を実行して情報が取得できるかも確認しておきます。


<body>
  <div id="app">
    <h1>Hello World</h1>
    <ul>
      <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script>
    Vue.createApp({
      setup() {
        const users = Vue.ref([]);
        fetch('https://jsonplaceholder.typicode.com/users')
          .then((response) => response.json())
          .then((data) => (users.value = data));
        return {
          users,
        };
      },
    }).mount('#app');
  </script>
</body>

ブラウザ上にはユーザの一覧が表示されます。Composition APIではcreatedがなくなりましたがsetup関数の中で実行することでOptions APIではcreatedの中で実行していたことができることが確認できました。

Composion APIではbeforeCreateに対応する処理はないのでcreatedと同様にsetup関数に直接記述することになります。