【Vue.js 3】v-bindの機能ぜんぶ書く

たまたまVue.jsの`v-bind`について強く調べる機会があって、実は`v-bind`の持つ全ての機能について説明してる記事って無いかも？と思ったので、「ぜんぶ」とまではいかないかもしれないけど思いつく範囲の`v-bind`の機能を列挙するつもりです。
Vue.jsのバージョンは3に限定します（v2も調べ直すの面倒なので）。

# `v-bind`の基本

Vue.jsのディレクティブの一つです。つまりテンプレートに記述するHTML属性っぽいところに記述できるVue.jsの構文の一つです。
ディレクティブがなんなのかのドキュメントは[ここ（英語）](https://v3.vuejs.org/guide/template-syntax.html#directives)です。
`v-bind`のAPIとしてのドキュメントは[ここ（英語）](https://v3.vuejs.org/api/directives.html#v-bind)です。

## 基本機能

HTML要素に使用すると、属性を動的に設定できます。コンポーネントに使用すると、コンポーネントのプロパティを動的に設定できます。

HTML要素に使用する例：

```vue
<template>
  <input v-bind:type="myInputType">
</template>
```

この場合、出力されるHTMLは`<input>`の`type`属性に、`myInputType`に保存された値が指定されます。
`myInputType`=`'password'`の場合に出力されるHTMLの具体例は次です。

```html
<input type="password">
```

コンポーネントに使用する例：

```vue
<template>
  <MyComponent v-bind:message="myMessage" />
</template>
```

このコードは、`<MyComponent>`コンポーネントの`message`プロパティに、`myMessage`に保存された値が指定されます。HTMLの結果はコンポーネントの定義によるので割愛します。

## 基本構文

HTMLでは`v-bind:【属性名】="【JavaScript式】"`のように記述します。記述は同じですがコンポーネントに使用すると`v-bind:【プロパティ名】="【JavaScript式】"`となります。

Vue.jsのディレクティブとして説明すると、ディレクティブ名が`bind`で、引数には属性名かプロパティ名を指定します。スクリプトにその属性かプロパティに与える値を取り出す式を記述するということになります。
（ディレクティブは`v-【ディレクティブ名】:【引数】.【修飾子】="スクリプト"`みたいな構造を持ちます。詳細は[こちら（英語）](https://v3.vuejs.org/guide/template-syntax.html#directives))

JavaScript式が書けるということは、次の例に書く`v-bind`は、いずれも有効な`v-bind`です。

```vue
<template>
  <input v-bind:type="myInputType">
  <input v-bind:type="myTypes.foo.bar">
  <input v-bind:type="types[kind]">
  <input v-bind:type="getType(foo)">
  <MyComponent v-bind:my-str-prop="prefix + str" />
  <MyComponent v-bind:my-num-prop="num * 42" />
  <MyComponent v-bind:my-obj-prop="{ foo: 1, bar: 2, baz }" />
  <MyComponent v-bind:my-arr-prop="[1, 2, 3, foo]" />
</template>
```

また`v-bind`には省略記法があり、
`:【属性名】="【JavaScript式】"`のように記述しても同じです。

```vue
<template>
  <input :type="myInputType">
  <input :type="myTypes.foo.bar">
  <input :type="types[kind]">
  <input :type="getType(foo)">
  <MyComponent :my-str-prop="prefix + str" />
  <MyComponent :my-num-prop="num * 42" />
  <MyComponent :my-obj-prop="{ foo: 1, bar: 2, baz }" />
  <MyComponent :my-arr-prop="[1, 2, 3, foo]" />
</template>
```

# 特別な使用

## `v-bind:class`

ドキュメントは[この辺り（英語）](https://v3.vuejs.org/guide/class-and-style.html#class-and-style-bindings)です。

### `v-bind:class`のオブジェクト形式

普通の`class`属性として考えると文字列のみ利用できそうですが、`v-bind:class`にはオブジェクトを与えることができます。
与えるオブジェクトは、`{ '【class名】': 【真偽値】 }`のような形式のオブジェクトです。真偽値が`true`の場合は、その`class`名が適用され`false`の場合は適用されません。条件によって`class`を制御する時に便利です（文字列結合と三項演算子など使用する必要はありません）。

```vue
<template>
  <div v-bind:class="{ 'item--active': isActive, 'item--hidden': !isShown }"></div>
</template>
```

`isActive`と`isShown`がどちらも`true`の時は`item--active`の値のみが`true`のため、`item--active`だけが`class`属性に付与されます。つまり次のHTMLが出力されます。

```html
<div class="item--active"></div>
```

### `v-bind:class`の配列形式

`v-bind:class`には配列を与えることができます。配列の要素には与える`class`名、およびオブジェクトを列挙します。すると配列で与えられた`class`名が全て適用されます。

例：

```vue
<template>
  <div v-bind:class="['foo','bar']"></div>
  <div v-bind:class="['foo','bar', { 'item--active': isActive, 'item--hidden': !isShown }]"></div>
</template>
```

HTMLの出力：

```html
<div class="foo bar"></div>
<div class="foo bar item--active"></div>
```

### 通常の`class`属性と併用

通常の属性は、同じ属性に対して、`v-bind`と、普通の属性としての記述は併用できませんが、`class`属性と次に紹介する`style`属性は、併用することができます。

`v-bind:class="..."`で与える`class`名と、`class="..."`で与える`class`名はマージして適用されます。

例：

```vue
<template>
  <div
    class="foo bar"
    v-bind:class="['baz','qux']">
  </div>
</template>
```

HTMLの出力：

```html
<div class="foo bar baz qux"></div>
```

## `v-bind:style`

ドキュメントは[この辺り（英語）](https://v3.vuejs.org/guide/class-and-style.html#class-and-style-bindings)です。

### `v-bind:style`のオブジェクト形式

`v-bind:style`もオブジェクトを与えることができます。
与えるオブジェクトは、`{ '【style名】': '【styleの値】' }`のような形式のオブジェクトです。文字列結合を使用して、`style`を頑張って作り出す必要はありません。また、プロパティ名に指定する【style名】はキャメルケースでも有効です。

```vue
<template>
  <div v-bind:style="{ color: myColor, fontSize: myFontSize }"></div>
</template>
```

`myColor`が`"red"`で`myFontSize`が`"14px"`の場合、次のHTMLが出力されます。

```html
<div style="color: red; font-size: 14px;"></div>
```

### `v-bind:style`の配列形式

`v-bind:style`には配列を与えることができます。配列の要素には与えるオブジェクトを列挙します。すると配列で与えられた`style`が全て適用されます。

例：

```vue
<template>
  <div v-bind:style="[{ color: myColor }, { fontSize: myFontSize }]"></div>
</template>
```

HTMLの出力：

```html
<div style="color: red; font-size: 14px;"></div>
```

### 自動プレフィックス

`v-bind:style`とオブジェクトを使用して与えたスタイル名は、そのブラウザで動作するために必要なベンダープレフィックスを自動的に付与します。

例：

```vue
<template>
  <div v-bind:style="{ 'line-clamp': 3 }"></div>
</template>
```

HTMLの出力（Chromeの場合）：

```html
<div style="-webkit-line-clamp: 3;"></div>
```

### 通常の`style`属性と併用

`v-bind:style`と`style`属性は、併用することができます。

`v-bind:style="..."`で与える`style`と、`style="..."`で与える`style`はマージして適用されます。

例：

```vue
<template>
  <div
    style="color: red;"
    v-bind:style="{ fontSize: '14px' }">
  </div>
</template>
```

HTMLの出力：

```html
<div style="color: red; font-size: 14px;"></div>
```

## `v-bind:key`

ドキュメントは[この辺り（英語）](https://v3.vuejs.org/api/special-attributes.html#key)です。
（本当は`key`属性（特別な属性）は`v-bind`とは関係ないですが、併用することが多いと思うので書いておきます。）

`key`属性を与えると、Vueは実際のDOMを描画するためのヒントとして使用します。よく使用される例をいくつか書いておきます。
ちなみに`key`属性は実際のHTMLには出力されません。

### `v-for`と使用

`v-for`一緒に使用する場合は、最適化が主な目的です。  
ドキュメントは[この辺り（英語）](https://v3.vuejs.org/guide/list.html#maintaining-state)です。

```vue
<template>
  <li v-for="item in items"
      v-bind:key="item.id">
    {{ item.text }}
  </li>
</template>
```

`v-for`が`<template>`にある場合は、`v-bind:key`は`<template>`に記述します。

```vue
<template>
  <ul>
    <template v-for="item in items"
        v-bind:key="item.id">
      <li>{{ item.text1 }}</li>
      <li>{{ item.text2 }}</li>
    </template>
  </ul>
</template>
```

### `<transition-group>`と使用

`<transition-group>`でアニメーションを意図通りに動かすためには、`<transition-group>`の子要素で`key`を使用します。
ドキュメントは[この辺り（英語）](https://v3.vuejs.org/guide/transitions-list.html#list-transitions)です。

```vue
<template>
  <transition-group name="list">
    <span v-for="item in items" v-bind:key="item.id">
      {{ item.text }}
    </span>
  </transition-group>
</template>
```

# より高度な構文

先に書いた、基本構文以外の構文的な機能を書きます。

## 動的属性名（プロパティ名）

`v-bind`はディレクティブなので[動的引数](https://v3.vuejs.org/guide/template-syntax.html#dynamic-arguments)を使用できます。
つまり、`v-bind:【名前】`で指定していた【名前】は動的に変更できます。

```vue
<template>
  <input v-bind:[foo]="bar">
</template>
```

これは`foo`が`"data-hello-bind"`で、`bar`が`"BAR DATA"`だった場合に出力されるHTMLの具体例は次です。

```html
<input data-hello-bind="BAR DATA">
```

もちろん省略記法でも使用できます。

```vue
<template>
  <input :[foo]="bar">
</template>
```

## 引数無し

`v-bind="object"`と指定するとオブジェクトのプロパティとして与えた値を一括で適用します。

つまり、

```vue
<template>
  <input v-bind="{ type:'text', maxlength: 3 }">
</template>
```

は、HTMLでは、

```html
<input type="text" maxlength="3">
```

となります。

## `.camel`修飾子

`v-bind:【名前】`で指定した【名前】を、キャメルケースに変換して実際に与えるらしいです（自分は使ったことないです。SVGで現れるキャメルケースの属性名の時に有用という噂を聞いたことがありますがよく知りません）。
詳細は[こちら（英語）](https://v3.vuejs.org/api/directives.html#v-bind)を参照してください。

次のような形で使用します。

```vue
<svg v-bind:view-box.camel="viewBox /* viewBox='0,0,0,0'*/"></svg>
```

出力はこのような形。

```html
<svg viewBox="0,0,0,0"></svg>
```

# より高度な使用

## `<slot>`に使用

スコープ付きスロットは`v-bind`を使用します。
ドキュメントは[この辺り（英語）](https://v3.vuejs.org/guide/component-slots.html#scoped-slots)です。

`<slot>`で`v-bind`を使用すると、子コンポーネントから`v-bind`で与えられた値を、スロットコンテンツを生成する親コンポーネント側で使用できます。これはスコープ付きスロットと言われます。

例：

```vue:ChildComponent
<template>
  <div class="child">
    <slot v-bind:foo="myFoo" />
  </div>
</template>
<script>
export default {
  data() {
    return { myFoo: "Hello" }
  }
}
</script>
```

```vue:ParentComponent
<template>
  <div class="parent">
    <ChildComponent>
      <template v-slot="{ foo }">
        {{ foo }} world
      </template>
    </ChildComponent>
  </div>
</template>
```

HTMLの出力：

```html
<div class="parent">
  <div class="child">
    Hello world
  </div>
</div>
```

詳細はドキュメントを参照してください。

## `v-bind:ref`

ドキュメントは[この辺り（英語）](https://v3.vuejs.org/api/special-attributes.html#ref)です。

普通に`ref="foo"`と書くと、これを書いた要素（またはコンポーネントのインスタンス）が`$refs.foo`経由でアクセスできるようになりますが、`v-bind:ref`を使用して、コールバック関数を与えるとコールバック関数によって、これを書いた要素（またはコンポーネントのインスタンス）を処理できます。

```vue
<template>
  <div v-bind:ref="(el) => child = el /* childプロパティで受け取ります */">Foo</div>
</template>
<script>
export default {
  data() {
    return { child: null }
  },
  mounted() {
    console.log(this.child) /* <div>Foo</div> が出力されます。 */
  }
}
</script>
```

## `v-bind:is`

ドキュメントは[この辺り（英語）](https://v3.vuejs.org/api/built-in-components.html#component)です。

`is`は`<component>`というVueの組み込みコンポーネントのプロパティです。
詳細はドキュメントを参照してください。

# HTML属性と値

いくつかの値は、それを与えるHTML属性によって、振る舞いが違います。
ドキュメントとしては[この辺り（英語）](https://vuejs.org/v2/guide/syntax.html#Attributes)と[RFCだとこれ（英語）](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0024-attribute-coercion-behavior.md)です。

## `null` または `undefined`

`null`か`undefined`を与えると属性自体が付与されません。属性ごと与えたくない（属性を削除）したい場合は、`null`か`undefined`を与えておけば良さそうです。
空白文字列(`''`)では属性自体はついたままなので要注意ということでもあります。

```vue:例①
<template>
  <input v-bind:type="nullValue">
  <input v-bind:type="undefinedValue">
  <input v-bind:type="falseValue">
  <input v-bind:type="emptyValue">
  <input v-bind:type="zeroValue">
</template>
<script>
export default {
  data () {
    return {
      nullValue: null,
      undefinedValue: undefined,
      falseValue: false,
      emptyValue: '',
      zeroValue: 0
    }
  },
}
</script>
```

```html:HTMLの出力
<input>
<input>
<input type="false">
<input type>
<input type="0">
```

## `false`

真偽値属性（例えば`<input>`の`disabled`）の場合は空白文字列(`''`)以外の`false`な値を与えると属性自体が付与されません。
こちらも空白文字列(`''`)では属性自体はついたままなので要注意です。特に属性としては`true`扱いであることに注意してください。

```vue:例②
<template>
  <input v-bind:disabled="nullValue">
  <input v-bind:disabled="undefinedValue">
  <input v-bind:disabled="falseValue">
  <input v-bind:disabled="emptyValue">
  <input v-bind:disabled="zeroValue">
</template>
<script>
export default {
  data () {
    return {
      nullValue: null,
      undefinedValue: undefined,
      falseValue: false,
      emptyValue: '',
      zeroValue: 0
    }
  },
}
</script>
```

```html:HTMLの出力
<input>
<input>
<input>
<input disabled>
<input>
```

# まとめ

`v-bind`の説明というより、Vueが特別に扱う属性とプロパティの機能説明って感じになってしまった気もしますね（汗）

再度書きますが、Vue 3限定なので、Vue 2では使用できないことも書いてあります。

他のディレクティブも書いてみたい気持ちもあったけど、`v-bind`だけで結構疲れたのでもういいかな。
しかし、本当にこれで全部なのかどうかは自信ないです。。