カスタムディレクティブ

基本

コアで出荷されたディレクティブのデフォルトセットに加えて、カスタムディレクティブ (custom directive) を登録することができます。カスタムディレクティブは任意の DOM の振舞いへのマッピングデータを変更するためのメカニズムを提供します。

Vue.directive(id, definition) メソッドで、directive iddefinition object を続けて渡して、グローバルカスタムディレクティブに登録できます。それをコンポーネントの directives オプションによってローカルカスタムディレクティブに登録することもできます。

フック関数

definition object はいくつかのフック関数(全て任意)を提供します:

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

一度登録された後は、以下のように Vue.js のテンプレート内で使用することができます (v- の接頭辞を追加するのを忘れないでください):

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

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

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

ディレクティブインスタンスのプロパティ

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

これらの全てのプロパティは読み込みのみ (read-only) で変更しないものとして扱わなくてはいけません。カスタムプロパティをディレクティブオブジェクトに追加することができますが、意図せずに既存の内部プロパティを上書きしないように注意が必要です。

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

<div id="demo" v-demo:hello.a.b="msg"></div>
Vue.directive('demo', {
bind: function () {
console.log('demo bound!')
},
update: function (value) {
this.el.innerHTML =
'name - ' + this.name + '<br>' +
'expression - ' + this.expression + '<br>' +
'argument - ' + this.arg + '<br>' +
'modifiers - ' + JSON.stringify(this.modifiers) + '<br>' +
'value - ' + value
}
})
var demo = new Vue({
el: '#demo',
data: {
msg: 'hello!'
}
})

結果

オブジェクトリテラル

あなたのディレクティブが複数の値を必要ならば、JavaScript オブジェクトリテラルも渡すことができます。ディレクティブは任意の妥当な JavaScript 式を取ることができるのを覚えておいてください:

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

リテラル修飾子

ディレクティブがリテラル修飾子 (literal modifier) で使用されるとき、属性の値は、プレーンな文字列として解釈され、そして直接 update メソッドに渡されます。update メソッドはプレーンな文字列はリアクティブにできないため、一度だけ呼ばれます。

<div v-demo.literal="foo bar baz">
Vue.directive('demo', function (value) {
console.log(value) // "foo bar baz"
})

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

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

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

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

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

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

<my-directive></my-directive>

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

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

高度なオプション

params

カスタムディレクティブは params 配列を提供でき、Vue コンパイラは自動的にディレクティブがバインドされた要素でこれらの属性を抽出します。例:

<div v-example a="hi"></div>
Vue.directive('example', {
params: ['a'],
bind: function () {
console.log(this.params.a) // -> "hi"
}
})

この API は動的な属性もサポートします。this.params[key] の値は自動的に最新に保ちます。加えて、値が変更されたときコールバックも指定できます:

<div v-example v-bind:a="someValue"></div>
Vue.directive('example', {
params: ['a'],
paramWatchers: {
a: function (val, oldVal) {
console.log('a changed!')
}
}
})

ディレクティブの params はJavaScript と HTML の間で同じキャメルケース⇔ケバブケースのマッピングに従うように、props と同様であることに注意してください。例えば、テンプレートで disable-effect として param を使用するためには、JavaScript で disableEffect としてそれにアクセスする必要があります。

deep

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

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

twoWay

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

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

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

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

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

terminal

1.0.19+

Vue は DOM ツリーを再帰的に渡り歩くことによってテンプレートをコンパイルします。しかしながら、コンパイル処理において ターミナル なディレクティブに遭遇した場合、要素の子を渡り歩くのを停止します。ターミナルなディレクティブは要素とその子のコンパイルの仕事を引き継ぎます。例えば、 v-ifv-for は両方ともターミナルなディレクティブです。

カスタムディレクティブを実装することは高度なトピックで、そして Vue のコンパイルパイプラインの知識を必要としますが、ターミナルなディレクティブを実装することは可能です。terminal: true を指定することによってカスタムターミナルディレクティブを指定することができます。また、おそらく部分的なコンパイルに対して Vue.Fragmentfactory を使用する必要があります。ここでは、コンパイルとページ上の他の場所にコンテンツテンプレートを”注入”するカスタムターミナルディレクティブの例を示します:

var FragmentFactory = Vue.FragmentFactory
var remove = Vue.util.remove
var createAnchor = Vue.util.createAnchor

Vue.directive('inject', {
terminal: true,
bind: function () {
var container = document.getElementById(this.arg)
this.anchor = createAnchor('v-inject')
container.appendChild(this.anchor)
remove(this.el)
var factory = new FragmentFactory(this.vm, this.el)
this.frag = factory.create(this._host, this._scope, this._frag)
this.frag.before(this.anchor)
},
unbind: function () {
this.frag.remove()
remove(this.anchor)
}
})
<div id="modal"></div>
...
<div v-inject:modal>
<h1>header</h1>
<p>body</p>
<p>footer</p>
</div>

カスタムターミナルディレクティブを実装したい場合、Vue 内部 のより良い理解を得るために、v-ifv-for のような組み込みのターミナルディレクティブのソースコードを読むことをお勧めします。

priority

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

いくつかのビルトインディレクティブに関する優先度は API で確認できます。さらに フロー制御するディレクティブ v-ifv-for は、コンパイル処理の中で常に最も高い優先度を持ちます。