Edited at

Vue.jsのカスタムディレクティブの使い方とフック関数のタイミングを理解する


はじめに

Vue.js(2.6.7)のカスタムディレクティブに関しての備忘録です。


そもそもディレクティブとは?

テンプレートに記述できるv-から始まる属性のこと。

様々なディレクティブが存在し、属性値に応じた DOM 操作ができる。

以下は DOM 操作の例。


  • 要素の表示制御

  • データバインディング

  • イベントリスナのアタッチ


ディレクティブの利用例

以下はv-show(値に応じて要素のstyle.displayプロパティを変更するディレクティブ)の利用例。

<div id="app">

<h1 v-show="ok">Hello!</h1>
</div>

const vm = new Vue({

el: '#app',
data: {
ok: true
}
});
// `ok`を`false`にすれば、`<h1 v-show="ok">Hello!</h1>`は非表示になる。
// vm.$data.ok = false;


See the Pen
Vue.js v-show
by soarflat (@soarflat)
on CodePen.

dataoktrueの場合、<h1>Hello!</h1>は表示され、falseの場合、<h1 style="display:none;">Hello!</h1>となり表示されない。

この他にも様々なディレクティブが存在する。詳細は公式ドキュメントを参照。

ディレクティブ


カスタムディレクティブとは?

自作したディレクティブ、もしくは自作したディレクティブを登録できる機能(仕組み)のこと。

カスタムディレクティブを利用することで、属性の付与もしくは属性値の変化に伴う DOM 操作を定義できる。


なぜカスタムディレクティブを利用するのか(カスタムディレクティブの使い所)

前述の通り、v-showは値に応じてstyle.displayプロパティを変更しているだけなので、この DOM 操作は自前でも定義できる。

しかし、ディレクティブという仕組みを利用することで、それぞれのコンポーネント(Vue インスタンス)に「値に応じてstyle.displayプロパティを変更する DOM 操作」を定義せずとも、共通の DOM 操作が可能になる。

そのため、複数のコンポーネント(Vue インスタンス)で独自の DOM 操作を共通化したい場合、カスタムディレクティブを定義する。


カスタムディレクティブの利用例

以下は、v-focusというカスタムディレクティブを定義する例。

ページを読み込む(Vue インスタンスをマウントする)とinput要素に自動でフォーカスが当たるようにする。

Vue.directive('focus', {

inserted(el) {
el.focus();
}
});

insertedはカスタムディレクティブと紐付いた要素が親 Node に挿入された時に呼ばれるフック関数。引数elには親 Node に挿入された要素が渡される。

カスタムディレクティブと要素を紐付けるためには、以下のように定義したv-focusを付与する。

<input v-focus>

今回の場合、このinput要素が親 Node(今回の場合はbody要素)に挿入された時にinsertedが呼び出され、引数elに挿入されたinput要素が渡される。

実際に動作するコードは以下の通り。

<input id="input" v-focus>

Vue.directive('focus', {

// `el`に`input`要素が渡されるので、それにフォーカスを当てる
inserted(el) {
el.focus();
}
});

new Vue({
el: '#input'
});


See the Pen
Vue.js CustomDirective example
by soarflat (@soarflat)
on CodePen.


ローカルディレクティブに登録する

directivesオプションを利用すれば、コンポーネント毎にカスタムディレクティブを登録できる。

<input id="input" v-focus>

new Vue({

el: '#input',
directives: {
focus: {
inserted(el) {
el.focus();
}
}
}
});


フック関数

前述のinsertedを含め、カスタムディレクティブでは以下のフック関数を利用できる。



  • bind: カスタムディレクティブが初めて対象の要素に紐付いた時に1度だけ呼ばれる。


  • inserted: カスタムディレクティブと紐付いた要素が親 Node に挿入された時に呼ばれる。


  • update: 紐付いた要素を抱合しているコンポーネントの VNode が更新される度に呼ばれる(子コンポーネントが更新される前に呼ばれる)。


  • componentUpdated: 紐付いた要素を抱合しているコンポーネントの VNode と子コンポーネントの VNode が更新された時に呼ばれる。


  • unbind: 紐付いた要素からディレクティブが取り除かれた時に呼ばれる。


それぞれのフック関数が呼ばれるタイミングを理解する

以下は全てのフック関数が呼ばれるサンプル。

<div id="app">

<h1 v-message v-if="message">{{ message }}</h1>
<button @click="update">update</button>
<button @click="remove">remove</button>
<button @click="init">init</button>
</div>

Vue.directive('message', {

bind(el) {
console.log('bind');
console.log('el.parentNode(bind)', el.parentNode);
},
inserted(el) {
console.log('inserted');
console.log('el.parentNode(inserted)', el.parentNode);
},
update(el) {
console.log('update');
console.log('el.innerHTML(update)', el.innerHTML);
},
componentUpdated(el) {
console.log('componentUpdated');
console.log('el.innerHTML(componentUpdated)', el.innerHTML);
},
unbind(el) {
console.log('unbind');
}
});

new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
update() {
this.message = 'Hello React!';
},
remove() {
this.message = '';
},
init() {
this.message = 'Hello Vue!';
}
}
});


See the Pen
Vue.js Hook Functions(Custom Directives)
by soarflat (@soarflat)
on CodePen.

それぞれのフック関数がどのタイミングで呼ばれるのかは、以下の通り。


  • ページロード時(Vue インスタンスを生成時): bindinserted

  • 「update」ボタンをクリック時: updatecomponentUpdated

  • 「remove」ボタンをクリック時: unbind

  • 「init」ボタンをクリック時(「remove」ボタンをクリックした後): bindinserted


ページロード時(Vue インスタンスを生成時)

bindinsertedが呼ばれて以下がコンソール出力される。

// bind

// el.parentNode(bind) null
// inserted
// el.parentNode(inserted) <div id=​"app">​…​</div>​

insertedは親 Node に挿入された時に呼ばれるため、parentNode<div id=​"app">​…​</div>​)が存在する。


「update」ボタンをクリック時

this.message"Hello React!"になる。

updatecomponentUpdatedが呼ばれて以下がコンソール出力される。

// update

// el.innerHTML(update) Hello Vue!
// componentUpdated
// el.innerHTML(componentUpdated) Hello React!

updateが呼ばれた時点では、子コンポーネントの VNode({{ message }})は更新されていないため、el.innerHTMLの結果は"Hello Vue!"

componentUpdatedが呼ばれた時は、子コンポーネントの VNode も更新された後のため、el.innerHTMLの結果は"Hello React!"


「remove」ボタンをクリック時

this.message""になる。

今回紐付けをした要素はv-if="message"を付与しており、このタイミングで<h1 v-message v-if="message">{{ message }}</h1>が破棄されunbindが呼ばれる。

// unbind


「init」ボタンをクリック時(「remove」ボタンをクリックした後)

this.message"Hello Vue!"になる。

<h1 v-message v-if="message">{{ message }}</h1>が再生成され、bindinsertedが呼ばれて以下がコンソール出力される。

// bind

// el.parentNode(bind) null
// inserted
// el.parentNode(inserted) <div id=​"app">​…​</div>​


フック関数に渡される引数

el以外にも複数の引数が渡される。詳細は公式ドキュメントを参照。

ディレクティブフック引数


終わり

間違いがあれば、ご指摘いただけると幸いでございます。