カスタムディレクティブ

最終更新日: 2019年6月3日

基本

Vue.js 本体で出荷されたディレクティブの標準セットに加えて (v-modelv-show)、カスタムディレクティブ (custom directive) を登録することができます。Vue 2.0 では、コードの再利用と抽象化における基本の形はコンポーネントです。しかしながら、通常の要素で低レベル DOM にアクセスしなければならないケースがあるかもしれません。こういった場面では、カスタムディレクティブが役立つでしょう。ひとつの例として、以下のような input 要素へのフォーカスが挙げられます:

ページを読み込むと、この要素はフォーカスを手に入れます。実際、このページに訪れてから他のところをクリックしていなければ、この input にフォーカス(注意:モバイル Safari では自動でフォーカスしません)が当たっているでしょう。さあ、これを実現させるディレクティブを作りましょう。

// `v-focus` というグローバルカスタムディレクティブを登録します
Vue.directive('focus', {
  // ひも付いている要素が DOM に挿入される時...
  inserted: function (el) {
    // 要素にフォーカスを当てる
    el.focus()
  }
})

代わりにローカルディレクティブに登録したいならば、コンポーネントの directives オプションで登録できます:

directives: {
  focus: {
    // ディレクティブ定義
    inserted: function (el) {
      el.focus()
    }
  }
}

これでテンプレートで、以下のような新しい v-focus 属性が使えるようになります。

<input v-focus>

フック関数

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

次のセクションで、これらのフックに渡す引数(すなわち el, binging, vnode, oldVnode)を見ていきましょう。

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

ディレクティブフックには以下の引数が渡せます:

el を除いて、これらの全てのプロパティは読み込みのみ (read-only) で変更しないものとして扱わなくてはいけません。フックを超えてデータを共有する必要がある場合は, 要素の dataset を通じて行うことが推奨されています。

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

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})

new Vue({
  el: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})

動的なディレクティブ引数

ディレクティブの引数は動的にできます。例えば、v-mydirective:[argument]="value" において、argument はコンポーネントインスタンスの data プロパティに基づいて更新されます!これにより、アプリケーション内でのカスタムディレクティブの利用が柔軟になります。

要素をページの固定位置にピン留めするカスタムディレクティブを作りたいとしましょう。縦方向のピクセル位置を値で設定するカスタムディレクティブを次のように作ることができます:

<div id="baseexample">
  <p>Scroll down the page</p>
  <p v-pin="200">Stick me 200px from the top of the page</p>
</div>
Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    el.style.top = binding.value + 'px'
  }
})

new Vue({
  el: '#baseexample'
})

これにより、ページの上端から 200px の位置に要素を固定できます。しかし、上端からではなく左端からの位置で要素をピン留めしたくなったらどうでしょうか?動的引数はコンポーネントのインスタンス毎に更新できるので、こういう場合に役立ちます:

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')
    el.style[s] = binding.value + 'px'
  }
})

new Vue({
  el: '#dynamicexample',
  data: function () {
    return {
      direction: 'left'
    }
  }
})

結果:

このカスタムディレクティブは、ちょっとした違いのユースケースを柔軟にサポートできるようになりました。

関数による省略記法

多くの場合、bindupdate には同じ振舞いが欲しいでしょうが、その他のフックに関しては気にかけません。例えば:

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

オブジェクトリテラル

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

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