はじめに
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
以外にも複数の引数が渡される。詳細は公式ドキュメントを参照。
終わり
間違いがあれば、ご指摘いただけると幸いでございます。