はじめに
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.
dataのokがtrueの場合、<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(inserted) Example by hira (@soar793) 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 インスタンスを生成時):
bind、inserted - 「update」ボタンをクリック時:
update、componentUpdated - 「remove」ボタンをクリック時:
unbind - 「init」ボタンをクリック時(「remove」ボタンをクリックした後):
bind、inserted
ページロード時(Vue インスタンスを生成時)
bindとinsertedが呼ばれて以下がコンソール出力される。
// 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!"になる。
updateとcomponentUpdatedが呼ばれて以下がコンソール出力される。
// 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>が再生成され、bindとinsertedが呼ばれて以下がコンソール出力される。
// bind
// el.parentNode(bind) null
// inserted
// el.parentNode(inserted) <div id="app">…</div>
フック関数に渡される引数
el以外にも複数の引数が渡される。詳細は公式ドキュメントを参照。
終わり
間違いがあれば、ご指摘いただけると幸いでございます。