なるはや Vue SPA入門：Vue の基本

Vue CLI3 で作成した SPA（Single Page Application）プロジェクト上で、段階的に Vue.js を学んで行きましょう。

[目次はこちら](https://qiita.com/tsubo/items/24df49ef2f1ba87984d1)

今回は Vue の基本編です。
やっていることは [Vue の公式サイトのガイド - 基本的な使い方](https://jp.vuejs.org/v2/guide/)と同じです。この記事では公式サイトほど全てを網羅していませんので、最終的には公式サイトにも目を通してください。

## 前提事項

[単一ファイルコンポーネント編](https://qiita.com/tsubo/items/6822cfb3f4369976db65) が完了していること。 

## Vue の基本

[Vue Router 編](https://qiita.com/tsubo/items/fe91e95e52ee82e05681)で作成した`src/views/SandBox.vue`ページ内で色々試してみたいと思います。 

### テンプレート構文
#### 展開
##### テキスト
データバインディングのもっとも基本的な形は、”Mustache” 構文(二重中括弧)を利用したテキスト展開です。

```html
<p>{{ msg }}</p>
```

mustache タグは、対応するオブジェクトの msg プロパティの値に置き換えられます。また、msg プロパティが変更される時、それに応じて更新されます。

##### 属性
Mustache は、HTML 属性の内部で使用することはできません。代わりに、v-bind ディレクティブを使用してください。

```html
<div v-bind:id="dynamicId"></div>
```

##### JavaScript 式の使用
これまで、テンプレートに単純なキーをバインディングしてきました。実際には Vue.js は全てのデータバインディング内部で JavaScript 式を完全にサポートします。

```html
<p>{{ 776 + 1 }}</p>

<p>{{ ok ? 'YES' : 'NO' }}</p>

<p>{{ msg.split('').reverse().join('') }}</p>
```

これらの式は、Vue インスタンスが所有するデータスコープ内で JavaScript として評価されます。制限として、それぞれのバインディングは、単一の式だけ含むことができるというものです。なので、以下は動作しません。

```html
<!-- これは文であり、式ではありません: -->
{{ var a = 1 }}

<!-- フロー制御もいずれも動作しません。三項演算子を使用してください -->
{{ if (ok) { return message } }}
```

#### ディレクティブ
ディレクティブは v- から始まる特別な属性です。ディレクティブ属性値は、単一の JavaScript 式を期待します(ただし、v-forは例外で、これについては後から説明します)。ディレクティブの仕事は、属性値の式が変化したときに、リアクティブに副作用を DOM に適用することです。

```html
<p v-if="seen">Now you see me</p>
```
ここでの v-if ディレクティブは seen 式の値が真か否かに基づいて、 `<p>` 要素を削除/挿入します。

##### 引数
ディレクティブの中には “引数” を取るものもあります。これはディレクティブ名の後にコロンで表記します。例えば、v-bindディレクティブは、リアクティブに HTML 属性を更新します。

```html
<a v-bind:href="url">Vue.js ガイド</a>
```

ここでの href は v-bind ディレクティブに要素の href 属性に式 url の値を束縛することを教えるための引数です。

v-on ディレクティブの別の例を見てみましょう。これは DOM イベントを受け取ります。

```html
<button v-on:click="log('クリックされました！')">Click Me!</button>

```

```js
export default {
  // ...
  methods: {
    // ...
    log(logMsg) {
      console.log(logMsg);
    }
  }
};
```



ここでの引数は受け取りたいイベント名です。ここからイベントハンドリングの詳細について説明します。

##### 修飾子

修飾子 (Modifier) は、ドットで表記された特別な接尾語で、ディレクティブが特別な方法で束縛されるべきということを示します。例えば、.prevent 修飾子は v-on ディレクティブに、イベントがトリガされた際 event.preventDefault() を呼ぶように伝えます。

```html
<form v-on:submit.prevent="onSubmit"> ... </form>
```


#### 省略記法

Vue は 2 つの最もよく使われるディレクティブ v-bind と v-on に対して特別な省略記法を提供しています。

##### v-bind 省略記法

```html
<!-- 完全な構文 -->
<a v-bind:href="url"> ... </a>

<!-- 省略記法 -->
<a :href="url"> ... </a>
```

##### v-on 省略記法

```html
<!-- 完全な構文 -->
<a v-on:click="doSomething"> ... </a>

<!-- 省略記法 -->
<a @click="doSomething"> ... </a>
```

### メソッド

mustache タグ内の JavaScript 式や click イベントリスナとして Vue インスタンスのメソッドを呼び出すことができます。

```vue
{{ now() }}

<button @click="log('クリックされました！')">Click Me!</button>
```
```js
export default {
  // ...
  methods: {
    now() {
      return new Date();
    },
    log(logMsg) {
      console.log(logMsg);
    }
  }
};
```

### 算出プロパティ

テンプレート内に式を書けるのはとても便利ですが、非常に簡単な操作しかできません。テンプレート内に多くのロジックを詰め込むと、コードが肥大化し、メンテナンスが難しくなります。そういった理由から、複雑なロジックには算出プロパティを利用すべきです。

```html
<p>Original message: "{{ msg }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
```

```js
export default {
  data() {
    return {
      msg: "Hello, World!",
      // ...
    };
  },
  // 算出プロパティ
  computed: {
    reversedMessage: function() {
      // `this` は vm インスタンスを指します
      return this.msg
        .split("")
        .reverse()
        .join("");
    }
  },
  // ...
};
```
#### 算出プロパティ vs メソッド

算出プロパティの代わりに、同じような関数をメソッドとして定義することも可能です。最終的には、2つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるという違いがあります。算出プロパティは、リアクティブな依存関係が更新されたときにだけ再評価されます。

### クラスとスタイルのバインディング
#### HTML クラスバインディング
##### オブジェクト構文
`v-bind:class` にオブジェクトを渡すことでクラスを動的に切り替えることができます。

```html
<div v-bind:class="{ active: isActive, 'text-danger': hasError }">
  オブジェクト構文(1)
</div>
```

次のようなデータがあったとすると

```js
data() {
  return {
    isActive: true,
    hasError: true
  }
}
```

このように描画されます。

```html
<div class="active text-danger">
  オブジェクト構文(1)
</div>
```

束縛されるオブジェクトはインラインでなくてもかまいません

```html
<div v-bind:class="classObject">
  オブジェクト構文(2)
</div>
```
```js
data() {
  return {
    classObject: {
      active: true,
      'text-danger': true
    }
  }
}
```

##### 配列構文
`v-bind:class` に配列を渡してクラスのリストを適用することができます。

```html
<div v-bind:class="[activeClass, errorClass]"></div>
```
```js
data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}
```

#### インラインスタイルのバインディング
##### オブジェクト構文

`v-bind:style` 向けのオブジェクト構文は非常に簡単です。JavaScript オブジェクトということを除けば、ほとんど CSS のように見えます。CSS のプロパティ名には、キャメルケース (camelCase) またはケバブケース (kebab-case: クォートとともに使うことになります) のどちらでも使用することができます。

```html
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">
  style オブジェクト構文(1)
</div>
```
```js
data() {
  return {
    activeColor: "orange",
    fontSize: 30
  }
}
```

テンプレートをクリーンにするために、直接 style オブジェクトに束縛するのは、よいアイディアです。

```html
<div v-bind:style="styleObject">
  style オブジェクト構文(2)
</div>
```
```js
data() {
  return {  
    styleObject: {
      color: "purple",
      fontSize: 24
    }
  }
}
```

##### 配列構文

`v-bind:style` 向けの配列構文は、同じ要素に複数のスタイルオブジェクトを適用することができます。

```html
<div v-bind:style="[baseStyles, overridingStyles]">
  style 配列構文
</div>
````
```js
data() {
  return {
    baseStyles: {
      color: "gray"
    },
    overridingStyles: {
      fontWeight: "bold",
      fontSize: "20px"
    }
  }
}
```

### 条件付きレンダリング
#### v-if
v-if ディレクティブは、ブロックを条件に応じて描画したい場合に使用されます。

```html
<div v-if="awesome">
  Vue is awesome!
</div>
<div v-else>
  Oh no
</div>
```

#### v-show
条件的に要素を表示するための別のオプションは v-show です。

```html
<h2 v-show="awesome">v-show</h2>
```

### リストレンダリング
#### v-for
配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。

```html
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.message }}
  </li>
</ul>
```

```js
data() {
  return {
    items: [
      { id: 1, message: "Foo" },
      { id: 2, message: "Bar" },
      { id: 3, message: "Baz" },
      { id: 4, message: "Hoge" },
      { id: 5, message: "Fuga" }
    ]
  };
}
```

### イベントハンドリング
#### イベントの購読
`v-on` ディレクティブを使うことで、DOM イベントの購読、イベント発火時の JavaScript の実行が可能になります。

```html
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
```
```js
data() {
  return {
    // ...
    counter: 0
  };
},
```

#### メソッドイベントハンドラ
多くのイベントハンドラのロジックはより複雑になっていくので、`v-on` 属性の値に JavaScript 式を記述し続けるのは現実的ではありません。そのため、`v-on` は呼び出したいメソッドの名前も受け付けます。

```html
<button v-on:click="greet">Greet</button>
```
```js
data() {
  return {
    // ...
    name: 'Vue.js'
  };
},
methods: {
  // ...
  greet(event) {
    // メソッド内の `this` は、 Vue インスタンスを参照します
    alert("Hello " + this.name + "!");
    // `event` は、ネイティブ DOM イベントです
    if (event) {
      alert(event.target.tagName);
    }
  }
}
```

#### インラインメソッドハンドラ
メソッド名を直接指定する代わりに、インライン JavaScript 式でメソッドを指定することもできます。

```html
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
```
```js
methods: {
  // ...
  say(message) {
    alert(message);
  }
}
```

#### イベント修飾子

イベントハンドラ内での `event.preventDefault()` または `event.stopPropagation()` の呼び出しは、様々な場面で共通に必要になります。これらはメソッド内部で簡単に呼び出すことができますが、DOM イベントの込み入った処理をおこなうよりも、純粋なデータロジックだけになっている方がより良いでしょう。

この問題に対応するために、Vue は `v-on` のためにイベント修飾子(event modifiers)を提供しています。修飾子は、ドット(.)で表記されるディレクティブの接尾辞を思い返してください。

- .stop
- .prevent
- .capture
- .self
- .once
- .passive

```html
<!-- クリックイベントの伝搬が止まります -->
<a v-on:click.stop="doThis"></a>

<!-- submit イベントによってページがリロードされません -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾子は繋げることができます -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 値を指定せず、修飾子だけ利用することもできます -->
<form v-on:submit.prevent></form>

<!-- イベントリスナーを追加するときにキャプチャモードで使います -->
<!-- 言い換えれば、内部要素を対象とするイベントは、その要素によって処理される前にここで処理されます -->
<div v-on:click.capture="doThis">...</div>

<!-- event.target が要素自身のときだけ、ハンドラが呼び出されます -->
<!-- 言い換えると子要素のときは呼び出されません -->
<div v-on:click.self="doThat">...</div>
```

#### キー修飾子

キーボードイベントを購読するにあたって、特定のキーのチェックが必要になることがあります。Vue では、`v-on` に対してキー修飾子を追加することができます。

```html
<!-- `key` が `Enter` のときだけ、`vm.submit()` が呼ばれます  -->
<input v-on:keyup.enter="submit">
```

### フォーム入力バインディング
#### 基本的な使い方
form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、`v-model` ディレクティブを使用することができます。

`v-model` は、内部的には input 要素に応じて異なるプロパティを使用し、異なるイベントを送出します。

テキストと複数行テキストは、value プロパティと input イベントを使用します。
チェックボックスとラジオボタンは、checked プロパティと change イベントを使用します。
選択フィールドは、value プロパティと change イベントを使用します。

##### テキスト

```html
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
```

##### 複数行テキスト

```html
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
```

##### チェックボックス

単体のチェックボックス

```html
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
```

複数のチェックボックス

```html
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
```
```js
data() {
  return {
    // ...
    checkedNames: []
  };
},
```

##### ラジオ

```html
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
```

##### 選択

単体の選択

```html
<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<span>Selected: {{ selected }}</span>
```
```js
data() {
  return {
    // ...
    selected: ""
  };
},
```

複数の選択

```html
<select v-model="selecteds" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<br>
<span>Selected: {{ selecteds }}</span>
```

#### 修飾子

##### .lazy
デフォルトでは、 `v-model` は各 input イベント (上記の IME 確定前を除いて) 後に、データと入力を同期します。 change イベント後に同期するように変更するために `lazy` 修飾子を追加することができます。

```html
<!-- "input" の代わりに "change" 後に同期します -->
<input v-model.lazy="msg" >
```

##### .number
ユーザの入力を数値として自動的に型変換したいとき、 `v-model` に管理された入力に `number` 修飾子を追加することができます。

```html
<input v-model.number="age" type="number">
```

##### .trim
ユーザの入力から空白を自動的に取り除きたいときは、 `v-model` に管理された入力に `trim` 修飾子を追加することができます。

```html
<input v-model.trim="msg">
```

## 演習

`src/views/SandBox.vue` で Vue.js の基本を色々試してみてください。
以下はサンプルコードです。値を変えたりして、どの様に画面が変化するか見てみてください。

```vue:SandBox.vue
<template>
  <div class="sandbox">
    <!-- ここにこのコンポーネントの HTML を書きます -->
    <h1>SandBox</h1>

    <div>
      <h2>テンプレート構文</h2>
      <div>
        <h3>テキスト</h3>
        <p>{{ msg }}</p>

        <h3>属性</h3>
        <p v-bind:id="dynamicId"></p>

        <h3>JavaScript</h3>
        <p>{{ 776 + 1 }}</p>
        <p>{{ ok ? 'YES' : 'NO' }}</p>
        <p>{{ msg.split('').reverse().join('') }}</p>
      </div>

      <div>
        <h3>ディレクティブ</h3>
        <p v-if="seen">Now you see me</p>

        <h3>引数</h3>
        <p><a v-bind:href="url">Vue.js ガイド</a></p>
        <p><button v-on:click="log('クリックされました！')">Click Me!</button></p>
      </div>

      <div>
        <h3>省略記法</h3>
        <p><a :href="url">Vue.js ガイド</a></p>
        <p><button @click="log('クリックされました！')">Click Me!</button></p>
      </div>
    </div>

    <div>
      <h2>メソッド</h2>
      <p>{{ now() }}</p>
      <p><button @click="log('クリックされました！')">Click Me!</button></p>
    </div>

    <div>
      <h2>算出プロパティ</h2>
      <p>Original message: "{{ msg }}"</p>
      <p>Computed reversed message: "{{ reversedMessage }}"</p>
    </div>

    <div>
      <h2>HTML クラスのバインディング</h2>
      <div v-bind:class="{ active: isActive, 'text-danger': hasError }">
        class オブジェクト構文(1)
      </div>
      <div v-bind:class="classObject">
        class オブジェクト構文(2)
      </div>
      <div v-bind:class="[activeClass, errorClass]">
        class 配列構文
      </div>
    </div>

    <div>
      <h2>インラインスタイルのバインディング</h2>
      <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">
        style オブジェクト構文(1)
      </div>
      <div v-bind:style="styleObject">
        style オブジェクト構文(2)
      </div>
      <div v-bind:style="[baseStyles, overridingStyles]">
        style 配列構文
      </div>
    </div>

    <div>
      <h2>条件付きレンダリング</h2>
      <div v-if="awesome">
        Vue is awesome!
      </div>
      <div v-else>
        Oh no
      </div>
      <h2 v-show="awesome">v-show</h2>
    </div>

    <div>
      <h2>リストレンダリング</h2>
      <ul>
        <li
          v-for="item in items"
          :key="item.id"
        >
          {{ item.message }}
        </li>
      </ul>
    </div>

    <div>
      <h2>イベントハンドリング</h2>
      <div>
        <h3>イベントの購読</h3>
        <button v-on:click="counter += 1">Add 1</button>
        <p>The button above has been clicked {{ counter }} times.</p>
      </div>
      <div>
        <h3>メソッドイベントハンドラ</h3>
        <button v-on:click="greet">Greet</button>
      </div>
      <div>
        <h3>インラインメソッドハンドラ</h3>
        <button v-on:click="say('hi')">Say hi</button>
        <button v-on:click="say('what')">Say what</button>
      </div>
    </div>

    <div>
      <h2>フォーム入力バインディング</h2>
      <div>
        <h3>テキスト</h3>
        <input
          v-model="message"
          placeholder="edit me"
        >
        <p>Message is: {{ message }}</p>
      </div>
      <div>
        <h3>複数行テキスト</h3>
        <span>Multiline message is:</span>
        <p style="white-space: pre-line;">{{ message }}</p>
        <br>
        <textarea
          v-model="message"
          placeholder="add multiple lines"
        ></textarea>
      </div>
      <div>
        <h3>チェックボックス</h3>
        <input
          type="checkbox"
          id="checkbox"
          v-model="checked"
        >
        <label for="checkbox">{{ checked }}</label>
      </div>
      <div>
        <h3>複数チェックボックス</h3>
        <div>
          <input
            type="checkbox"
            id="jack"
            value="Jack"
            v-model="checkedNames"
          >
          <label for="jack">Jack</label>
          <input
            type="checkbox"
            id="john"
            value="John"
            v-model="checkedNames"
          >
          <label for="john">John</label>
          <input
            type="checkbox"
            id="mike"
            value="Mike"
            v-model="checkedNames"
          >
          <label for="mike">Mike</label>
          <br>
          <span>Checked names: {{ checkedNames }}</span>
        </div>
      </div>
      <div>
        <h3>ラジオ</h3>
        <input
          type="radio"
          id="one"
          value="One"
          v-model="picked"
        >
        <label for="one">One</label>
        <br>
        <input
          type="radio"
          id="two"
          value="Two"
          v-model="picked"
        >
        <label for="two">Two</label>
        <br>
        <span>Picked: {{ picked }}</span>
      </div>
      <div>
        <h3>選択</h3>
        <select v-model="selected">
          <option
            disabled
            value=""
          >Please select one</option>
          <option>A</option>
          <option>B</option>
          <option>C</option>
        </select>
        <span>Selected: {{ selected }}</span>
      </div>
      <div>
        <h3>複数の選択</h3>
        <select
          v-model="selecteds"
          multiple
        >
          <option>A</option>
          <option>B</option>
          <option>C</option>
        </select>
        <br>
        <span>Selected: {{ selecteds }}</span>
      </div>
    </div>
  </div>
</template>

<script>
// ここに JavaScript を書きます

// このコンポーネントで使用する Vue オブジェクト
export default {
  // データ
  data() {
    return {
      // テキスト
      msg: "Hello, World!",
      // 属性
      dynamicId: "someId",
      // JavaScript
      ok: true,
      // ディレクティブ
      seen: true,
      url: "https://jp.vuejs.org/v2/guide",
      // HTML クラスのバインディング
      isActive: true,
      hasError: true,
      classObject: {
        active: true,
        "text-danger": true
      },
      activeClass: "active",
      errorClass: "text-danger",
      // インラインスタイルのバインディング
      activeColor: "orange",
      fontSize: 30,
      styleObject: {
        color: "purple",
        fontSize: "24px"
      },
      baseStyles: {
        color: "gray"
      },
      overridingStyles: {
        fontWeight: "bold",
        fontSize: "20px"
      },
      // 条件付きレンダリング
      awesome: true,
      // リストレンダリング
      items: [
        { id: 1, message: "Foo" },
        { id: 2, message: "Bar" },
        { id: 3, message: "Baz" },
        { id: 4, message: "Hoge" },
        { id: 5, message: "Fuga" }
      ],
      // イベントハンドリング
      counter: 0,
      name: "Vue.js",
      // フォーム入力バインディング
      message: "",
      checked: false,
      checkedNames: [],
      picked: "",
      selected: "",
      selecteds: []
    };
  },
  // 算出プロパティ
  computed: {
    reversedMessage: function() {
      // `this` は vm インスタンスを指します
      return this.msg
        .split("")
        .reverse()
        .join("");
    }
  },
  // メソッド
  methods: {
    now() {
      return new Date();
    },
    log(logMsg) {
      console.log(logMsg);
    },
    greet(event) {
      // メソッド内の `this` は、 Vue インスタンスを参照します
      alert("Hello " + this.name + "!");
      // `event` は、ネイティブ DOM イベントです
      if (event) {
        alert(event.target.tagName);
      }
    },
    say(message) {
      alert(message);
    }
  }
};
</script>

<style>
/* ここに、このコンポーネントに適用する CSS を書きます */
.active {
  font-weight: bold;
}
.text-danger {
  color: red;
}

/* 削除
.sandbox {
  color: red;
}
*/
</style>
```

## 次回

[コンポーネント](https://qiita.com/tsubo/items/745324a9f4719cb6b8fa)