本記事は Vue.js コンポーネント入門の第7回(最終回)です。
ライフサイクルフックを紹介します。
前提
PC | ブラウザ | Node | npm | Vue |
---|---|---|---|---|
macOS 10.12 | Firefox Quantum | 9.3.0 | 5.5.1 | 2.5 |
ライフサイクルフックとは
ライフサイクルフックとは、Vue コンポーネントが初期化されてから破棄されるまでの各タイミングで任意の処理を実行できるしくみです。
jQuery 風に言うと $(document).ready()
のようなものでしょうか。画面が初期化されたタイミングで Ajax でデータを取得してリストを構築したいとか、jQuery プラグインの初期化を実行したいとか、そういう開発ニーズがありますよね。
さて、ライフサイクルフックの実例はこのようなものです:
<script>
export default {
data () {
return {
users: [], // ユーザーのリスト
// ...
}
},
// created フックは Vue インスタンスが作成された直後に呼ばれる
created () {
// つまり Vue インスタンスが作成されたタイミングで API にアクセスし...
axios.get('/api/users')
.then(response => {
// データを取得してユーザーリストに代入する
// ちなみにフック内での this はコンポーネントを指す
this.users = response.data
// これにより UI が更新される
// 例えばユーザーのテーブルや連絡先カードのリストなど
})
.error(err => {
// ...
})
}
}
</script>
Vue コンポーネントのライフサイクルは大きく 4 段階に分かれます。
(1) Vue インスタンスの生成
フック:beforeCreate
created
まずは Vue インスタンスが生成されます。具体的には、new Vue()
が実行されたということです。この時点ではあくまで新しい JavaScript オブジェクトが作成されただけです。画面にはまだコンポーネントの内容は反映されていません。
インスタンスの生成前に beforeCreate
フックが呼ばれ、インスタンスの生成後に created
フックが呼ばれます。
(2) DOM へのマウント
フック:beforeMount
mounted
生成された Vue インスタンスは、続いてコンポーネントとして DOM にマウントされます。つまり、コンポーネントが HTML 要素の一員として画面に描画されるということです。
マウント前に beforeMount
フックが呼ばれ、マウント後に mounted
フックが呼ばれます。
(3) データの変更〜画面の更新
フック:beforeUpdate
updated
コンポーネントが DOM にマウントされて画面に描画された後は、コンポーネントはデータの変更を待ち受けます。コンポーネントの data
が内部的に変更された場合や、props
で渡されているデータが親コンポーネント側で(つまり外部的に)変更された場合には、それらの値の変更に合わせた形で画面も自動的に更新し描きかえます。
画面が描きかわる前に beforeUpdate
フックが呼ばれ、画面が描きかわった後に updated
フックが呼ばれます。
(4) Vue インスタンスの破棄
フック:beforeDestroy
destroyed
v-if
や v-for
の機能によりインスタンスが表示されなくなるタイミングで destroy 系のフックが呼ばれます。
インスタンスが破棄される前に beforeDestroy
フックが呼ばれ、インスタンスが破棄された後に destroyed
フックが呼ばれます。
そのほかのフック
大きくは上記の 4 段階に分かれますが、そのほかにもフックが用意されています。activated
deactivated
errorCaptured
です。応用的な内容ですので今回は説明を割愛します。
全てのライフサイクルフックについては、ライフサイクルダイアグラム および API マニュアルをご覧ください。
created と mounted のユースケース
実際の開発シーンでは最もよく使うであろう created
と mounted
について、ユースケースの例や使い分けかたを説明していきます。
ポイントは created
のタイミングでは DOM にアクセスできないが、mounted
のタイミングでは DOM にアクセスできるという点です。
created
ユースケース例:Ajax 通信を行いデータを初期化する
データベースと繋がるシングルページアプリケーションを作成する際には頻出なパターンでしょう。コード例は前節「ライフサイクルフックとは」の冒頭に挙げた通りです。
初期化のタイミングという意味では mounted
で Ajax 通信を行ってもほぼ同じように動きますが、なぜ created
の方がいいのかというと、created
の方がタイミング的に(ごく僅かではあれ)前だからです。少しでも早く非同期通信を始めておいた方が、DOM マウントが行われ画面描画の準備が完了してからデータを画面に反映するまでの待ち時間が減るでしょう。
mounted
ユースケース例: Vue ではないプラグインを初期化する
世の中には便利な JavaScript ライブラリがたくさん存在しますが、そのほとんどがピュアな JavaScript で書かれているかもしくは jQuery プラグインでしょう。これらのライブラリは Vue コンポーネントの中でも簡単に使うことができますが、初期化の際にはターゲットとなる DOM 要素が必要とされることがあります。
$('.js-example').select2();
var ctrl = new ScrollMagic.Controller();
new ScrollMagic.Scene({ duration: 100, offset: 50 }).setPin("#my-element").addTo(ctrl);
そういった場合には mounted
に初期化処理を記述します。created
ではまだ DOM にアクセスできないので、上記でいうと例えば "#my-element"
を取得できないのです。
マニュアルの「ラッパーコンポーネント の例」がまさにこれです。
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
})
}
ちなみに上記の例で利用されている this.$el
は、自分の Vue コンポーネントのルート DOM 要素を返します。
<template>
<select>
<slot></slot>
</select>
</template>
上記のテンプレートの場合は this.$el
は <select>
要素を返します。ただし mounted
より前のタイミングではまだ DOM にマウントされていないため、$el
も利用できないということです。
HTML 要素の高さや幅を取得する場合も同様で、DOM 要素にアクセスする必要がある初期化処理は mounted
に記述しましょう。
サンプルアプリケーション
第5回で作成したマークダウンコメントパネルに少し手を加えます。SimpleMDE という便利なライブラリを見つけたので、入力欄にそちらを適用して改良します。
前回は簡素なテキストエリアでしたが、少しそれっぽい見た目になったのではないでしょうか。
追加パッケージのインストール
# 第5回で作成したchapter-5ディレクトリで作業します。
$ npm install --save simplemde
入力欄コンポーネント
変更するのは入力欄コンポーネント(MarkdownEditor.vue
)のみです。
<template>
<div class="form-group">
<textarea id="MarkdownEditorTextarea"></textarea>
</div>
</template>
<script>
import SimpleMDE from "simplemde"
export default {
props: {
value: { type: String, required: true }
},
data () {
return {
editor: null
}
},
mounted () {
this.editor = new SimpleMDE({
element: document.getElementById("MarkdownEditorTextarea"),
status: false,
toolbar: [
"bold", "italic", "heading", "|",
"quote", "unordered-list", "ordered-list", "|",
"link", "image", "|",
"guide"
]
})
this.editor.codemirror.on("change", () => {
this.$emit("input", this.editor.value())
})
},
updated () {
this.editor.value(this.value)
}
}
</script>
SimpleMDE をコンポーネントに適合させるため、mounted
フックを使いました。
まず mounted
で、SimpleMDE エディターを適用させる textarea に対して初期化を行っています。
this.editor = new SimpleMDE({
element: document.getElementById("MarkdownEditorTextarea"),
// 以下略
また、エディター内のデータが変更するたびに『 input
イベントで親コンポーネントに入力値を通知する』処理を登録しています。
this.editor.codemirror.on("change", () => {
this.$emit("input", this.editor.value())
})
余談
今回は第5回のサンプルを改良しましたが、コンポーネントを分割しておいたおかげで変更箇所が特定しやすく、また他のコンポーネントに影響を与えていない点に注目してください。
入力欄の変更の影響は入力欄コンポーネントに局所化されています。うまく部品化できているということです。入力欄コンポーネントは v-model
に対応する、つまり『 value
を受け取り input
イベント発行で入力値を通知する』というインターフェースを守っている限りはどのように見た目や機能を変えても他の部品に影響を与えることはありません。
今回のテーマであるライフサイクルフックからは離れた話題でしたが、部品化の概念を理解するきっかけになればと思います。
本記事では Vue.js コンポーネント入門の第7回(最終回)としてライフサイクルフックを紹介しました。
まだ紹介していない機能はたくさんあります。しかしながら基本的な機能は紹介していますので、次または現在進行中のプロジェクトに導入する足がかりになれば幸いです。
というか基本的な機能だけでも結構パワフルなので、紹介した機能を組み合わせて応用するだけでも色々なものが作れるのではないでしょうか。
あとはマニュアルを読み進めれば発展的な機能まで把握できるはずです。最近は技術書も出版され始めているようですが、Vue のマニュアルは内容も豊富ですし最新版がすぐ日本語に翻訳されているので主な情報ソースとして十分だと思います。
関連記事
Vue.js コンポーネント入門(全7回)
- Vue.jsコンポーネント入門 (1) 環境設定
- Vue.jsコンポーネント入門 (2) コンポーネントとは何か?
- Vue.jsコンポーネント入門 (3) propsによるデータの受け渡し
- Vue.jsコンポーネント入門 (4) $emitによるイベントの発行
- Vue.jsコンポーネント入門 (5) コンポーネント間のコミュニケーション
- Vue.jsコンポーネント入門 (6) slotによるコンテンツの差し込み
- Vue.jsコンポーネント入門 (7) ライフサイクルフック