Vue.jsを１から学んでみた〜カスタムディレクティブ〜

## 11-1 カスタムディレクティブとは

Vue.jsで用意してくれているディレクティブ（`v-on`,`v-bind`等）の他に、
自分で作成したディレクティブ（これが**カスタムディレクティブ**）を定義＆使用できる。

### 簡単な例（グローバル登録）
画面が表示された時に最初にフォーカスされる`v-focus`というカスタムディレクティブを定義、使用します。

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// グローバル登録：カスタムディレクティブ
Vue.directive('focus', {
  // フック関数
  inserted: function (el) {
    // ディレクティブフックに引数でelを渡し、
    // 要素にフォーカスを当てる
    el.focus()
  }
})

new Vue({
  render: h => h(App),
}).$mount('#app')
```

```App.vue
<template>
  <div>
    <Job></Job>
  </div>
</template>

<script>
import Job from "./components/Job.vue";

export default {
  data: function() {
    return {
      defaultAge: 15,
      defaultCheck: true
    }
  },
  components: {
    Job: Job
  }
}
</script>
```

```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <input type="checkbox">
    <!-- こちらにフォーカスがあたる -->
    <input type="text" v-focus>
  </div>
</template>
```
*結果*
![aaa22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/f895d72e-0e45-49f7-046d-5d957d203245.gif)

### 簡単な例（ローカル登録）

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
```

```App.vue
<template>
  <div>
    <Job></Job>
  </div>
</template>

<script>
import Job from "./components/Job.vue";

export default {
  data: function() {
    return {
      defaultAge: 15,
      defaultCheck: true
    }
  },
  components: {
    Job: Job
  }
}
</script>
```

```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <input type="checkbox">
    <!-- こちらにフォーカスがあたる -->
    <input type="text" v-focus>
  </div>
</template>

<script>
export default {
  directives: {
    // ローカル登録：カスタムディレクティブ
    focus: {
      inserted: function (el) {
    // ディレクティブフックに引数でelを渡し、
    // 要素にフォーカスを当てる
        el.focus()
      }
    }
  }
}
</script>
```
*結果*
![aaa22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/f895d72e-0e45-49f7-046d-5d957d203245.gif)

### フック関数

カスタムディレクティブには、（Vueインスタンスのライフサイクルフックのような）用意された関数がある。

```main.js
Vue.directive('focus', {
  // bind(), inserted(), update(), componentUpdated(), unbind()
  // 上記5つが用意されている。

  bind(el, binding, vnode) {
    // 画面が表示された時に1回だけ実行されるメソッド
  },
  inserted(el, binding, vnode) {
    // 親Nodeに挿入された時に実行されるメソッド
  },
  update(el, binding, vnode, oldNode) {
    // 子コンポーネントが更新される前に実行されるメソッド
  },
  componentUpdated(el, binding, vnode, oldNode) {
    // 子コンポーネントが更新される後に実行されるメソッド
  },
  unbind(el, binding, vnode, oldNode) {
    // ディレクティブが紐づいている要素から削除される時に1回だけ実行されるメソッド
  }
})
```


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

フック関数には、以下の引数を渡せる。

* el：ディレクティブのが紐づく要素。**VNodeではなく、DOM要素を直接操作できる**。
* binding：オブジェクト。以下のプロパティを持つ
  * name: `v-` 無しのディレクティブ名。`v-focus`であれば`focus`。
  * value: ディレクティブに渡される値。`v-focus="'1+1'"`であれば`1+1`という文字列。**ダブルクォーテーション内はJavaScript式として判断される**ため、文字列を指定する場合はシングルクォーテーションが必要。
  * oldValue: *update と componentUpdated* においてのみ利用できる、更新前の値。
  * expression: ディレクティブに渡される値を文字列として評価する。`v-focus="1+1"`は、valueの場合`2`、expressionの場合`1+1`となる。
  * arg: ディレクティブに渡される引数。`v-focus:hoge`であれば`hoge`。一つしか設定できない。
  * modifiers: 修飾子 (modifier) を含んでいるオブジェクト。設定された場合、trueが設定される。`v-focus:hoge.foo.zoo`の場合、`{ foo: true, zoo: true}`となる。
* vnode：紐づくVNode＝仮想ノード。
* oldValue：`update()`,`componentUpdated()`のみで利用できる。変更前のVnode。

### 省略記法

実際のところ、フック関数は`bind()`、`update()`が多く使われる。
このため、Vue.jsは以下のような省略記法が用意される。

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

実際の開発でもほぼこれが使われるだろうと思う。

## 11-2 引数：el

elは、直接DOMを操作できる。

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

Vue.directive('color-swatch', function (el) {
  // elを使い、背景色を赤色に。
  el.style.backgroundColor = 'red'
})

new Vue({
  render: h => h(App),
}).$mount('#app')
```

```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <input type="text" v-color-swatch>
  </div>
</template>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/7b8ac3f9-77f4-7c54-efd3-e40d6297f0eb.png)

## 11-3 引数：binding

### value（単一）

カスタムディレクティブに値を渡すことで、bindingに設定される

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

Vue.directive('color-swatch', function (el, binding) {
  // カスタムディレクティブの
  // bindingを使って背景色を変更する
  el.style.backgroundColor = binding.value
})

new Vue({
  render: h => h(App),
}).$mount('#app')
```
```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <!-- 値を指定する。JavaScriptを指定するので文字列はシングルクォーテーション -->
    <input type="text" v-color-swatch="'blue'">
  </div>
</template>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/5ae770b7-e069-97bf-788e-dac302195afc.png)

### value（複数）

カスタムディレクティブに**オブジェクト**を渡すことで、bindingに設定される


```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

Vue.directive('color-swatch', function (el, binding) {
  // オブジェクトのkeyを指定する
  el.style.backgroundColor = binding.value.color
  el.style.width = binding.value.width
})

new Vue({
  render: h => h(App),
}).$mount('#app')
```
```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <!-- 値を指定する。JavaScriptを指定するので文字列はシングルクォーテーション -->
    <input type="text" v-color-swatch="{color: 'black', width: '15px'}">
  </div>
</template>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/a6c11026-99fa-4219-7dca-25da15d3315a.png)

### arg

ディレクティブに渡される引数。`v-focus:hoge`であれば`hoge`。一つしか設定できない。

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value.color
  el.style.width = binding.value.width
  // .argで引数の文字列をそのまま取得できる
  el.style.borderStyle = binding.arg
})

new Vue({
  render: h => h(App),
}).$mount('#app')
```
```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <!-- :dotted のように引数を指定する -->
    <!-- 引数の指定は一つだけしか許可されていない -->
    <input type="text" v-color-swatch:dotted="{color: 'white', width: '200px'}">
  </div>
</template>
```

*結果：dottedがinputに反映される*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/11ea917d-d71a-4f7e-abd3-a5c847337440.png)

### modifiers

修飾子 (modifier) を含んでいるオブジェクト。設定された場合、trueが設定される。`v-focus:hoge.foo.zoo`の場合、`{ foo: true, zoo: true}`となる。

```main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

 if (binding.modifiers.round) {
   // modifirs.修飾子名で評価できる
   el.style.borderRadius = "500rem"
 }

})

new Vue({
  render: h => h(App),
}).$mount('#app')
```
```Job.vue
<template>
  <div>
    <p>私はエンジニアです</p>
    <!-- .roundのように修飾子を指定する -->
    <!-- 複数指定する場合は、:dotted.round.mount のように、.でつなげる -->
    <input type="text" v-color-swatch:dotted.round="{color: 'white', width: '200px'}">
  </div>
</template>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/0d58b29f-b0ff-d952-0774-ea8aca949560.png)