Vue.jsには、属性の形式でDOMツリー(文書ツリー)を操作する仕組みとしてディレクティブが用意されています。
ディレクティブには、データを属性やpropsにバインドするv-bindディレクティブや、イベントとメソッド(処理)をバインドするv-onディレクティブなどがあります。
Vue.jsでは、ディレクティブを使用することで、コードからDOMを直接操作することなく、Webアプリケーションとしての動作を与えることができるようになります。
基本的なディレクティブは、あらかじめVue.jsに用意されていますが、複雑なアプリケーションを作成していると、標準で用意されているディレクティブだけでは不十分な場合が出てきます。
そこでVue.jsには、ディレクティブを定義するための仕組みが用意されています。
今回は、アプリケーション固有のディレクティブを定義する方法について紹介します。
目次
ディレクティブの自作(カスタム)
ディレクティブは、コンポーネントのイベントハンドラーや算出プロパティなどで代替できることがほとんどですが、実装するアプリケーションの仕様によっては、標準のディレクティブなどでは対応することができずにコンポーネント内でDOMを操作するコードを記述しなければならない場合があります。
しかし、コード内にDOMを操作するコードを記述する方法を安易に選択するのはあまり好ましいことではありません。
Vue.jsはコード内でDOMを操作することを前提としたフレームワークではなく、DOMの操作はVue.jsが行い、アプリケーション側ではDOMを操作する元になるデータを操作するようになっています。
コード内にDOMを操作する処理を記述してしまうと、ビューとロジックとが絡み合って入り組んだ構造になってしまいます。
ビューとロジックが絡み合っていると、アプリケーションのメンテナンスがしづらくなり、コードを改変した際に予期しない不具合が発生することもあります。
また、ビューとロジックが分離していないため、単体テストが実施しにくい環境になってしまうこともあります。
ですので、コードでDOMを操作する方法をとるのではなく、DOMを操作する部分を自作のディレクティブに切り出す方法を検討します。
ディレクティブの定義
自作のディレクティブを定義する際は、directive関数を使用します。
directive関数を使用すると、Vueのインスタンスに独自のカスタムディレクティブを登録することができます。
ディレクティブの書式
カスタムディレクティブは、以下のように定義します。
1 2 3 4 |
const app = Vue.createApp({}) app.directive('カスタムディレクティブの名前', { // ここにディレクティブの実装を記述する }) |
Vueインスタンスにdirectiveメソッドでディレクティブを登録します。
directiveメソッドの1つ目のパラメーターにはディレクティブの名前を指定します。(コンポーネントを定義する際と同様です。)
2つ目のパラメーターには、DOMを操作するための処理を記述します。
DOM操作には、ライフサイクルフックを利用することができます。
ディレクティブで使用できるライフサイクルフック
カスタムディレクティブで使用できるライフサイクルフックには以下のものがあります。
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeUnmount
- unmounted
1 2 3 4 5 6 7 8 9 |
const MyDirective = { created: function(el, binding, vnode, prevVnode) {}, beforeMount: function(el, binding, vnode, prevVnode) {}, mounted: function(el, binding, vnode, prevVnode) {}, beforeUpdate: function(el, binding, vnode, prevVnode) {}, updated: function(el, binding, vnode, prevVnode) {}, beforeUnmount: function(el, binding, vnode, prevVnode) {}, unmounted: function(el, binding, vnode, prevVnode) {} } |
createdはVue3.xで追加されました。
createdは要素の属性やイベントリスナーが適用される前に呼び出されます。
beforeMountはVue2.x時のbindから変更されました。
beforeMountは要素は要素がマウントされる前(ディレクティブが要素にバインドされる時)に発生します。
mountedはVue2.x時のinsertedから変更されました。
mountedは要素は要素がマウントされた後(要素が親DOMに挿入された後)に発生します。
beforeUpdateはVue3.xで追加されました。
beforeUpdateは要素自体が更新される前に発生します。
updatedはVue2.x時のcomponentUpdatedから変更されました。
updatedは要素自体が更新された後に発生します。
beforeUnmountはVue3.xで追加されました。
beforeUnmountは要素のマウントが解除される直前に発生します。
unmountedはVue2.x時のunbindから変更されました。
unmountedはディレクティブが削除されると発生します。
Vue.2.xにあったupdateはVue3.xで削除されました。(updatedに統合されました。)
ディレクティブの各フック受け取る引数には、以下のものがあります。
- el
- binding
- vnode
- prevVnode
elはディレクティブがバインドされる要素になります。
これを利用することでDOMを直接操作することができます。
bindingは以下のプロパティを持つオブジェクトです。
- instance: ディレクティブが使われているコンポーネントのインスタンスです。
- value: ディレクティブにバインドされた値です。「1 + 2」のような式がバインドされている場合は、「3」のような計算結果の値になります。
- oldValue: 以前の値です。このプロパティはbeforeUpdateおよびupdateでのみ利用可能です。値が変更されているかどうかを判別する際に使用できます。
- arg: ディレクティブに引数がある場合は、それを含むオブジェクトです。例えば「v-my-directive:abc」のような指定がある場合は「abc」になります。
- modifiers: ディレクティブに修飾子がある場合は、それを含むオブジェクトです。例えば「v-my-directive:abc.def」のような指定がある場合は「{abc: true, def: true}」のようなオブジェクトになります。
- dir: ディレクティブが登録された時にパラメーター(directive関数の第2引数)として渡されるオブジェクトです。
vnodeはelで受け取った実際のDOM要素のノードを表します。
prevVnodeは以前のvnodeです。この引数はbeforeUpdateおよびupdateでのみ利用可能です。
カスタムディレクティブの実装例
ここでは、カスタムディレクティブの実装例をいくつか示します。
カスタムディレクティブの実装例1
ここでは、ページが読み込まれて表示された時にフォーカスが当たるテキストボックス(input要素)を独自のディレクティブの「v-focus」を使って実装します。
1 2 3 4 5 6 7 8 |
<div id="app"> <div> テキストボックス1: <input /> </div> <div> テキストボックス2: <input v-focus /> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 |
// Vueインスタンスを作成します。 const app = Vue.createApp({}) // グローバルカスタムディレクティブの「v-focus」を登録します。 app.directive('focus', { mounted: function(el) { // 要素にフォーカスを当てます。 el.focus(); } }) // Vueインスタンスにルート要素をマウントします。 app.mount('#app') |
mountedの引数(el)に要素を受けて、focusメソッドで要素にフォーカスを当てています。
カスタムディレクティブの実装例2
ここでは、ディレクティブを指定した要素の背景色を設定する独自のディレクティブの「v-highlight」を実装します。
1 2 3 4 5 |
<div id="app"> <p v-highlight="color"> {{message}} </p> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Vueインスタンスを作成します。 const app = Vue.createApp({ data: function() { return { color: '#abcdef', message: 'Hello Vue.js!' } } }) // グローバルカスタムディレクティブの「v-highlight」を登録します。 app.directive('highlight', { beforeMount: function(el, binding, vnode, prevVnode) { // 要素の背景色を設定します。 el.style.background = binding.value; } }) // Vueインスタンスにルート要素をマウントします。 app.mount('#app') |
mountedの第1引数のelに要素を受けます。第2引数にはバインドされた値を受けます。
ここでは、elのスタイル(背景色)にバインドした値(dataオプションに定義したcolor)を設定しています。
ローカルディレクティブ
ディレクティブはコンポーネントと同様に、ローカルに登録することができます。
ローカルディレクティブを定義することで、特定のVueインスタンス内でのみ有効なディレクティブを作成することができます。
1 2 3 4 5 6 7 8 9 10 11 |
Vue.createApp({ // ローカルカスタムディレクティブの「v-focus」を登録します。 directives: { focus: { mounted: function(el) { // 要素にフォーカスを当てる el.focus(); } } } }).mount('#app') |
ローカルディレクティブを定義する際は、directivesプロパティオプションを使用します。
ここでは、先のグローバルディレクティブの例で示した、ページが表示された時にフォーカスを当てる「v-focus」ディレクティブを定義しています。