カスタムディレクティブ

基本

Vue.js ではカスタムディレクティブを登録する仕組みが用意されています。カスタムディレクティブはデータの変更に伴い DOM がどのように変更されるかを定義することができる仕組みです。directive id とそれに続く definition objectVue.directive(id, definition) メソッドに渡して、グローバルにカスタムディレクティブを登録することができます。この definition object はいくつかの hook 関数(全て任意)を提供します:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.directive('my-directive', {
bind: function () {
// 準備のための作業をします
// e.g. イベントリスナを追加したり、一回だけ実行が必要なコストのかかる処理を行う
},
update: function (newValue, oldValue) {
// 更新された値に何か処理をします
// この部分は初期値に対しても呼ばれます
},
unbind: function () {
// クリーンアップのための処理を行います
// e.g. bind()の中で追加されたイベントリスナの削除
}
})

一度登録された後は、以下のように Vue.js のテンプレート内で使用することができます (Vue.js の prefix が必要です):

1
<div v-my-directive="someValue"></div>

update 関数のみが必要な場合は、definition object の代わりに関数を1つ渡すこともできます:

1
2
3
Vue.directive('my-directive', function (value) {
// この関数は update() として使用される
})

全ての hook 関数は実際の directive object にコピーされます。directive object は hook 関数の内側で this のコンテキストとしてアクセスすることができます。この directive object はいくつかの便利なプロパティを持っています:

これらの全てのプロパティは read-only で変更しないものとして扱わなくてはいけません。カスタムプロパティを directive object に追加することができますが、意図せずに既存の内部プロパティを上書きしないように注意が必要です。

いくつかのプロパティを使用したカスタムディレクティブの例:

1
<div id="demo" v-demo="LightSlateGray : msg"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.directive('demo', {
bind: function () {
this.el.style.color = '#fff'
this.el.style.backgroundColor = this.arg
},
update: function (value) {
this.el.innerHTML =
'name - ' + this.name + '<br>' +
'raw - ' + this.raw + '<br>' +
'expression - ' + this.expression + '<br>' +
'argument - ' + this.arg + '<br>' +
'value - ' + value
}
})
var demo = new Vue({
el: '#demo',
data: {
msg: 'hello!'
}
})

結果

複数の節

コンマで区切られた引数は、複数のディレクティブインスタンスとしてバウンドされます。以下の例は、ディレクティブメソッドが2回呼ばれます。

1
<div v-demo="color: 'white', text: 'hello!'"></div>

オブジェクトリテラルで値を閉じることにより、すべての引数で単一のバインディングを成し遂げることができます:

1
<div v-demo="{color: 'white', text: 'hello!'}"></div>
1
2
3
Vue.directive('demo', function (value) {
console.log(value) // Object {color: 'white', text: 'hello!'}
})

リテラルディレクティブ

もしカスタムディレクティブを作成するときに isLiteral: true を渡した場合は、その属性値は文字列 string として扱われ、そのディレクティブの expression として割り当てられます。リテラルディレクティブはデータの監視の準備はしません。

例:

1
<div v-literal-dir="foo"></div>
1
2
3
4
5
6
Vue.directive('literal-dir', {
isLiteral: true,
bind: function () {
console.log(this.expression) // 'foo'
}
})

動的リテラル

しかし、リテラルディレクティブに mustache タグを含んでいる場合は、以下のような挙動になります:

Two-way ディレクティブ

もしディレクティブが受け取ったデータを Vue インスタンスに書き戻したい場合は twoWay: true を渡す必要があります。このオプションはディレクティブの this.set(value) で使用することができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.directive('example', {
twoWay: true,
bind: function () {
this.handler = function () {
// vm にデータをセットします
// もしディレクティブが v-example="a.b.c" と紐付いている場合,
// 与えられた値を `vm.a.b.c` に
// セットしようと試みます
this.set(this.el.value)
}.bind(this)
this.el.addEventListener('input', this.handler)
},
unbind: function () {
this.el.removeEventListener('input', this.handler)
}
})

インラインステートメント

acceptStatement:true を渡すことでカスタムディレクティブが v-on が行っているようなインラインステートメントを使用できるようになります:

1
<div v-my-directive="a++"></div>
1
2
3
4
5
6
7
8
Vue.directive('my-directive', {
acceptStatement: true,
update: function (fn) {
// 呼び出される際に渡される値は function です
// function は "a++" ステートメントを
// 所有者の vm のスコープで実行します
}
})

ただし、テンプレート内のサイドエフェクトを避けるためにも、賢く使いましょう。

ディープな監視

もしカスタムディレクティブでオブジェクトを扱いたい場合で、オブジェクトの内側のネストされたプロパティが変更された時に update をトリガしたい場合は、ディレクティブの定義に deep: true を渡す必要があります。

1
<div v-my-directive="obj"></div>
1
2
3
4
5
6
7
Vue.directive('my-directive', {
deep: true,
update: function (obj) {
// `obj` の中のネストされたプロパティが
// 変更された時に呼ばれる
}
})

ディレクティブの優先度

ディレクティブには任意で優先度の数値 (デフォルトは0) を与えることができます。同じ要素上で高い優先度をもつディレクティブは他のディレクティブより早く処理されます。同じ優先度をもつディレクティブは要素上の属性のリストに出現する順番で処理されますが、ブラウザが異なる場合、一貫した順番になることは保証されません。

いくつかのビルトインディレクティブに関する優先度は API リファレンス で確認できます。さらに ロジック制御する v-repeatv-if は “ターミナルディレクティブ” として扱われ、コンパイル処理の中で常に最も高い優先度を持ちます。

エレメントディレクティブ

いくつのケースでは、属性としてよりむしろカスタム要素の形でディレクティブを使いたい場合があります。これは、Angular の “E” モードディレクティブの概念に非常に似ています。エレメントディレクティブは軽量な代替を本格的なコンポーネントとして提供します(ガイドの後半で説明されています)。カスタム要素をディレクティブのように登録できます:

1
2
3
4
5
6
Vue.elementDirective('my-directive', {
// 標準のディレクティブのような同じ API
bind: function () {
// this.el を操作 ...
}
})

この時、以下の代わりに:

1
<div v-my-directive></div>

以下のように書くことができます:

1
<my-directive></my-directive>

エレメントディレクティブは引数または expressions を受け付けることはできません。しかし、その振舞いを決定するために要素の属性を読み取ることはできます。

標準のディレクティブとの大きな違いは、エレメントディレクティブはターミナルで、Vue が一度エレメントディレクティブに遭遇したことを意味します。それは、要素とその子を残したまま、エレメントディレクティブそれ自体、要素とその子を操作することができるようになります。

次は カスタムフィルタ