Vue.jsを１から学んでみた〜Vue CLIとVue Component〜

[Vue.js を１から学んでみた。](https://qiita.com/sh-watanabe/items/c7fc086a4ea5c707fbd3)にて書いてきましたが、
長くなり編集しにくくなってきたので、分割しました（こんなやりかたしないのかな？知りたい。）

## 8-1. Vue CLIとは

Vueのフレームワーク。
簡単に利用でき、サーバーを立ち上げることができる。

```
$ vue create vue-cli-sample


Vue CLI v4.4.6
? Please pick a preset: default (babel, eslint)


Vue CLI v4.4.6
✨  Creating project in /Users/shinri/development/study/vuejs/vue-cli-sample.
🗃  Initializing git repository...
⚙️  Installing CLI plugins. This might take a while...

・・・省略

$ cd vue-cli-sample/
$ npm run serve

> vue-cli-sample@0.1.0 serve /Users/shinri/development/study/vuejs/vue-cli-sample
> vue-cli-service serve

 INFO  Starting development server...
98% after emitting CopyPlugin

 DONE  Compiled successfully in 2287ms                                                                                                                                                                                       13:24:23


  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.100.16:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```

http://localhost:8080/ にアクセスするとVueの画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/7b8ca4f6-1ca9-886b-c198-dd73c8be0b85.png)

## 8-2. Vue CLI構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/4c2f16fc-57aa-8251-8b9c-5c3737622e72.png)

### 初期ディレクトリ概要

* node_modules
  * nodeでインストールされた様々なモジュール群
  * 何か便利なライブラリを見つけたらnpmでインストールするが、ここに入る
* public
  * 静的なファイルを置いておく。webpackの対象にならない。
  * index.html しか初期は入っていない（favicon除く）。
  * あんまり追加しない。
* src
  * vueファイルを書いてアプリを作る（雑）
  * webpackの対象となる

### 初期ファイル概要

* .gitIgnore
  * gitにpushしないファイル/ディレクトリの指定
* babel.config.js
  * ES5にする時の指定。
  * 初期設定のままで基本はOK。複雑なものを作る場合は別。
* package.json
  * 使用するライブラリのバージョンを指定する
  * scriptsでは、コマンドのオプションを指定する。`$npm run serve`は、`"serve": "vue-cli-service serve"`のように定義されている
* package-lock.json
  * git等で複数人で開発する場合、こちらを使ってインストール・・・という曖昧な認識。
  * 「[package-lock.jsonについて知りたくても聞けなかったこと](https://qiita.com/fj_yohei/items/7ca887a45e0855917279)」を参考にさせていただくのがベスト。

### srcディレクトリ詳細

#### *assets*ディレクトリ

ロゴファイルなど、リソースを入れる。

#### *main.js*
render関数で、App.vueオブジェクトを指定しているのみ。
 #appにマウントしているが、これは*public/index.html*に指定されている。
つまり、App.vueオブジェクトをindex.htmlに表示させていることになる。


```public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <!-- ↓ここに#appが指定されている -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

```src/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*

**単一ファイルコンポーネント**と呼ばれる。
画面に表示する内容を、このコンポーネントで実装する。

*なお、.vueファイルはVue CLIで使われることで、webpackにて単一コンポーネントとして扱われる。*
`import App from './App.vue'`のように定義すると、.vueファイル内で単一コンポーネントとして使用できる。

以下の初期作成の例では、HellowWorld.vueコンポーネントを作成し、
それをApp.Vueでタグで使うことで画面を表示させている。

```src/App.Vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
```

```src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

```

### 本番用ビルド

`$ npm run build` を実行することで、src,publicの内容が最適化ビルドされ、distディレクトリが作成される。
これをそのまんまリリースできる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/c374a01f-1ffe-07d4-1310-542fce67d594.png)


## 8-3. Vueコンポーネントの基本

Vueコンポーネント（.vueファイル）は、以下で構成される。

* template
  * DOMとして展開される内容を記述する箇所
  * scriptにrender関数があれば、不要。
  * **注意点：ルート要素は必ず１つであること。エラーになる**
* script
  * JavaScriptで記載する箇所
  * 特に必要なければ、不要。
* style
  * CSSを記載する箇所
  * CSSが不要であれば、不要。

templateとscriptだけを書いて簡単なvueにて実行してみる

```App.vue
<template>
  <p>こんにちは{{name}}さん</p>
</template>

<script>
export default {
  data: function() {
    return {
      name: "太郎"
    }
  }
}
</script>

<!-- styleは消しました -->
```

http://localhost:8080/ にアクセスするとVueの画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/07eb564b-5f59-a392-335c-b2583c3e0940.png)

### **補足：ルート要素は１つ**
以下のように、`template`配下にルート要素を2つ以上持つと、コンパイルエラーとなる。

```hoge.vue
<template>
  <p>こんにちは{{name}}さん</p>
  <p>こんにちは{{name}}さん</p>
</template>
```

```コンソール
 error  in ./src/FirstName.vue?vue&type=template&id=50ec6832&

Module Error (from ./node_modules/vue-loader/lib/loaders/templateLoader.js):
(Emitted value instead of an instance of Error) 

  Errors compiling template:

  Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

  1  |  
  2  |  <p>こんにちは{{name}}さん</p>
     |                         
  3  |  <p>こんにちは{{name}}さん</p>
     |  ^^^^^^^^^^^^^^^^^^^^^^
  4  |  
```

> Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

一つにしなさいと怒られます。ただ、シンプルに機能を分けるサポートをしてくれるという意味では助かることかと。


## 8-4. Vueコンポーネントのグローバル登録

どこの.vueでも単一コンポーネントを使えるように、グローバル登録を行う。
グローバル登録は`main.js`に対して行う。

以下のように、`FirstName.vue`を新規作成し、グローバル登録した後、そのコンポーネントを使用する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/4917afdc-a4e1-0883-648d-3b3b5d80eb8b.png)


```FirstName.vue
<template>
  <p>こんにちは{{name}}さん</p>
</template>

<script>
export default {
  data: function() {
    return {
      name: "次郎"
    }
  }
}
</script>
```

```App.vue
<template>
  <!-- グローバル登録したコンポーネントを使用 -->
  <FirstName></FirstName>
</template>
```

```main.js
import Vue from 'vue'
import App from './App.vue'
// コンポーネント追加
import FirstName from './FirstName.vue'

Vue.config.productionTip = false

// グローバル登録
Vue.component("FirstName", FirstName);

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

```

*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/373558c6-f25d-6b68-a264-a1eddb90279d.png)



## 8-5. Vueコンポーネントのローカル登録

グローバル登録の次にローカル登録について。
単一コンポーネントのローカル登録と同じく、Js内（今回で言うと、`<script>`内）でimportし、vueインスタンスの`compnent`に登録する。

以下のように、`LastName.vue`を新規作成し、ローカル登録した後、そのコンポーネントを使用する。
※`main.js`と`FirstName.vue`は変わらないので割愛。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/0726fc2f-7be6-14e5-5a34-3c27e4334cac.png)

```LastName.vue
<template>
  <div>
    <p>私は{{name}}です</p>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      name: "山田"
    }
  }
}
</script>
```

```App.vue
<template>
  <!-- ルートは一つ -->
  <div>
    <!-- グローバル登録したコンポーネントを使用 -->
    <FirstName></FirstName>
    <!-- ローカル登録したコンポーネントを使用 -->
    <LastName></LastName>
  </div>
</template>

<script>
import LastName from "./LastName.vue";

export default {
  components: {
    LastName: LastName
  }
}
</script>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/64bda78c-718e-6a8f-d542-20671f88a59d.png)

## 8-6. VueコンポーネントのCSS

`<style>`タグにCSSを書けば良いが、必ずscopedをつける。
そうすることで、同じvue内のtemplateにのみ適用させることができる。

```FirstName.vue
<template>
  <div>
    <p>こんにちは{{name}}さん</p>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      name: "次郎"
    }
  }
}
</script>

<!-- `scoped`をつける -->
<style scoped>
div {
  border: 1px blue solid;
}
</style>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/ab5667a7-fe71-ac5d-c43c-6adbd094d7d5.png)

内部的にみると、該当タグに識別子をつけて判断している感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/f1b29a45-199a-836b-fcf2-0d07616d37fb.png)


**scopedを付けないと、全`div`に反映されてしまう**

```
<!-- `scoped`をつけない -->
<style>
div {
  border: 1px blue solid;
}
</style>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/a7cd6201-b14f-a99f-8bff-79864f6835fd.png)

## 8-7. コンポーネント間のデータの受け渡し（親→子:props）

### 基本：プリミティブ/配列/オブジェクト

親→子の場合、`props`を使います。
例として、`App.vue`から数値と文字列を受け渡すやり方を載せます。
**注意：propsは変更値不可なので、子画面で利用する場合は変数に詰め替えてから使う。**

```FirstName.vue
<template>
  <div>
    <p>こんにちは{{name}}さん</p>
    <button @click="changeMethod">押した分だけ年齢増えます</button>
    <p>年齢：{{age}}</p>
    <p>性別：{{gender}}</p>
  </div>
</template>

<script>
export default {
  // 親→子の受け渡しに`props`を使う。
  // 子では受け口として定義するイメージ。
  // 書き方は配列かオブジェクトで可能。
  // ただ、詳しく書きたいのであれば、オブジェクト型とする。
  props: {
    // 変数名：キャメルケース
    yourAge: {
      type: Number,  // 型
      required: true // 必須かどうか
    },
    gender: {
      type: String,
      required: true
    }
  },
  data: function() {
    return {
      name: "次郎",
      // propsのデータは変更不可が推奨されているため、
      // このようにdataに設定する
      age: this.yourAge
    }
  },
  methods:{
    changeMethod: function() {
      // propsの値は変更させない
      this.age += 1
    }
  },
}
</script>

<style scoped>
div {
  border: 1px blue solid;
}
</style>
```

```App.vue
<template>
  <div>
    <!-- ケバブケースでOK -->
    <!-- propsの値を設定する。v-bindも勿論OK! -->
    <FirstName :your-age="defaultAge" gender="男性"></FirstName>
    <LastName></LastName>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```
*結果*
![aaa10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/a7957d3c-9777-f0c9-08d1-11899434036d.gif)

### 応用：slot

htmlをそのまんま渡す場合、
v-htmlタグを使って頑張ればいけそうだけど、めんどくさい笑
その代わりにVueコンポーネント内に（DOM要素のように）htmlを書けるようにすることができる機能がある。

そのために`slot`タグと`v-slot`ディレクティブを使う ~~（ジャグラー打ちてー）~~。

#### **1つslotの例**

```LastName.vue
<template>
  <div>
    <!-- slotタグを指定 -->
    <slot></slot>
  </div>
</template>
```

```App.vue
<template>
  <div>
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>
    <!-- 要素にHTMLを指定 -->
    <LastName>
      <p>私は山田です</p>
      <p>職業はSEです</p>
    </LastName>
    <p>思っていた年齢{{defaultAge}}歳でしたが、{{emitData}}歳違っていたようでした。すみません。</p>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      emitData: 0
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```

*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/5a72d38b-ddf5-fb2f-a77d-524f543bb14b.png)

#### **複数のslotの例**

結論から言うと、`template`タグと、`v-slot`ディレクティブを使う。

```LastName.vue
<template>
  <div>
    <!-- slotタグのnameにテンプレート名を指定 -->
    <slot name="name"></slot>
    <hr>
    <!-- slotタグのnameにテンプレート名を指定 -->
    <slot name="job"></slot>
  </div>
</template>
```

```App.vue
<template>
  <div>
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>
    <LastName>
      <!--  v-slotでテンプレート名を指定 -->
      <template v-slot:name>
        <p>私は山田です</p>
      </template>
      <!--  v-slotでテンプレート名を指定 -->
      <template v-slot:job>
        <p>職業はSEです</p>
      </template>
    </LastName>
    <p>思っていた年齢{{defaultAge}}歳でしたが、{{emitData}}歳違っていたようでした。すみません。</p>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      emitData: 0
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```
*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/3765eb5b-58ba-33fa-c01b-fd7fcc809d16.png)

#### **デフォルトslotの例**

`template`がついていないものは、Vue.jsが`<template v-slot:defalut>`を勝手に作成しまとめる。
あくまでも、`slot`を使う場合は`template`が必要ということ。

```App.vue
<template>
  <div>
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>
    <LastName>
      <!-- templateがついていないのは、デフォルトスロットとしてまとめられる１ -->
      <p>デフォルトスロット用１</p>
      
      <!--  v-slotでテンプレート名を指定 -->
      <template v-slot:name>
        <p>私は山田です</p>
      </template>
      <!--  v-slotでテンプレート名を指定 -->
      <template v-slot:job>
        <p>職業はSEです</p>
      </template>

      <!-- templateがついていないのは、デフォルトスロットとしてまとめられる2 -->
      <p>デフォルトスロット用２</p>
    </LastName>
  </div>
</template>
```

```LastName.vue
<template>
  <div>
    <!-- デフォルトスロット表示用 -->
    <slot></slot>
    <hr>
    <!-- slotタグのnameにテンプレート名を指定 -->
    <slot name="name"></slot>
    <hr>
    <!-- slotタグのnameにテンプレート名を指定 -->
    <slot name="job"></slot>
  </div>
</template>
```

*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/d745e5cf-51a8-30ba-b9c2-a25a2d60d800.png)

#### **スロットプロパティ**

**スロットプロパティ**を使えば、子のdataを使うことができる。

* 親の指定
  * `<template v-slot:name="slotProps">` ←`"slotProps"`がスロットプロパティ（の名称）。
  * 好きな命名でOK。
* 子の指定：`<slot name="title" :name="name">` ←v-bindを使って、スロットプロパティを指定する。
* 子はVueインスタンスのフィールドを使用して、親のスロットプロパティが受け取るイメージ。

```LastName.vue
<template>
  <div>
    <!-- デフォルトスロット表示用 -->
    <slot></slot>
    <hr>
    <!-- v-bindでスロットプロパティを定義 -->
    <slot name="name" :sei="sei" :mei="mei"></slot>
    <hr>
    <!-- slotタグのnameにテンプレート名を指定 -->
    <slot name="job" :syokugyou="syokugyou"></slot>
  </div>
</template>

<script>
export default {
  props: {
    job: {
      type: String,
      required: false
    }
  },
  data: function() {
    return {
      sei: "山田",
      mei: "太郎",
      syokugyou: this.job
    }
  }
}
</script>
```

```App.vue
<template>
  <div>
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>
    <LastName :job="job">
      <!--  スロットプロパティ「slotProps」を定義 -->
      <template v-slot:name="slotProps">
        <!-- このタグでのみ「slotProps」使用できる -->
        <p>私は{{slotProps.sei}}{{slotProps.mei}}です</p>
      </template>

      <!--  スロットプロパティ「hoge」を定義 -->
      <template v-slot:job="hoge">
        <!-- このタグでのみ「hoge」を 使用できる -->
        <p>職業は{{hoge.syokugyou}}です</p>
      </template>
    </LastName>
    <p>思っていた年齢{{defaultAge}}歳でしたが、{{emitData}}歳違っていたようでした。すみません。</p>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      emitData: 0,
      job: "家庭教師"
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```

*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/358f9b6a-fdfc-02fd-3dde-2db3cfe690f0.png)

#### **スロットプロパティ（デフォルトスロットのみ）**

デフォルトスロットのみの場合、templateタグが不要（Vue.jsが勝手にtemplateタグを付与してくれる）。
その時のスロットプロパティは、単一コンポーネントに定義する。

```LastName.vue
<template>
  <div>
    <!-- デフォルトスロット表示用 -->
    <slot :sei="sei" :mei="mei" :syokugyou="syokugyou"></slot>
  </div>
</template>

<script>
export default {
  props: {
    job: {
      type: String,
      required: false
    }
  },
  data: function() {
    return {
      sei: "山田",
      mei: "太郎",
      syokugyou: this.job
    }
  }
}
</script>
```
```App.vue
<template>
  <div>
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>

    <!-- デフォルトスロットのときのスロットプロパティの命名 -->
    <LastName :job="job" v-slot:default="slotProps">
    <!-- <LastName :job="job" v-slot="slotProps"> のようにdefalutは省略可-->

      <!-- templateがなくデフォルトスロットのみ -->
      <p>私は{{slotProps.sei}}：{{slotProps.mei}}です</p>
      <p>職業は{{slotProps.syokugyou}}です</p>
    </LastName>
    <p>思っていた年齢{{defaultAge}}歳でしたが、{{emitData}}歳違っていたようでした。すみません。</p>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      emitData: 0,
      job: "広告業界"
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```

*結果*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/86355b1c-740a-4afe-9519-c74d99314eab.png)

#### **v-slotの省略記法**

`#`に置き換えることができる

`<template v-slot:job="hoge">`
を
`<template #job="hoge">`
このように省略して書ける。

ただし、defaultスロットについては省略はNG。

## 8-8. コンポーネント間のデータの受け渡し（子→親:$emit）

子→親の場合、`$emit`を使います。
元々、`$emit`は、カスタムイベントを作る時に使用される。
任意のタイミングで`$emit`を実行することで、親のVueコンポーネント（今回は`App.vue`）でイベントを発火させる。
その発火する時に、データを付け加えて戻すような仕組み。
**重要：子は親のデータを変更することはできない。あくまでも子からデータを返している。**

なお、カスタムイベントはJavaScript内では使われない。
よって（htmlは大文字小文字を区別しないので）、ケバブケースで命名するのが良い。

```FirstName.vue
<template>
  <div>
    <p>こんにちは{{name}}さん</p>
    <button @click="changeMethod">押した分だけ年齢増えます</button>
    <p>年齢：{{age}}</p>
    <p>性別：{{gender}}</p>
  </div>
</template>

<script>
export default {
  props: {
    yourAge: {
      type: Number,
      required: true
    },
    gender: {
      type: String,
      required: true
    }
  },
  data: function() {
    return {
      name: "次郎",
      age: this.yourAge
    }
  },
  methods:{
    changeMethod: function() {
      this.age += 1

      // 子→親への値受け渡し。
      // $emitで値を返す。正確いうと、親のイベントを発火させて、引数で渡す。
      // 第１引数：イベント名（ケバブケースで指定する）、第２引数：イベントで渡す値（任意）
      // 元々 $emit はカスタムイベントを作る時に使用。
      this.$emit("my-click", this.age - this.yourAge)
    }
  },
}
</script>

<style scoped>
div {
  border: 1px blue solid;
}
</style>
```

```App.vue
<template>
  <div>
    <!-- v-onディレクティブでイベントを受け取る -->
    <FirstName :your-age="defaultAge" gender="男性" @my-click="emitData = $event"></FirstName>
    <LastName></LastName>
    <p>思っていた年齢{{defaultAge}}歳でしたが、{{emitData}}歳違っていたようでした。すみません。</p>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      emitData: 0
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```
*結果*
![aaa11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/62587290-5c5c-7ba7-af4a-afbaba9d524a.gif)


## 8-8. コンポーネントの動的切り替え

同じ場所に表示するコンポーネントを切り替える時には、`component`タグを使う。
以下、新たに ParentコンポーネントとChildコンポーネントを定義し、切り替える。

```Parent.vue
<template>
  <div>
    <p>親の山田一郎です</p>
  </div>
</template>
```
```Child.vue
<template>
  <div>
    <p>子供の山田一太郎です</p>
  </div>
</template>
```
```App.vue
<template>
  <div>
    <!-- クリックでコンポーネントを切り替える操作 -->
    <button @click="changeComponent('Parent')">押したら親の名前が表示されます</button>
    <br>
    <button @click="changeComponent('Child')">押したら子供の名前が表示されます</button>
    <hr>

    <!-- compenentタグで動的にVueコンポーネントを切り替える -->
    <component :is='currentComponents'></component>
  </div>
</template>

<script>
import Parent from "./components/Parent.vue";
import Child from "./components/Child.vue";

export default {
  data: function() {
    return {
      currentComponents: "Parent"
    }
  },
  components: {
    Parent: Parent,
    Child: Child
  },
  methods:{
    changeComponent: function(compName) {
        this.currentComponents = compName
    }
  },
}
</script>
```
*結果*
![aaa12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/ffea0672-450b-ca64-1598-6a45dae822d2.gif)

#### 切り替え時のキャッシュ

**なお、切り替えの度にVueインスタンスが`destroy`と`create`を繰り返している。**
インスタンスが消えてしまうため、dataが毎回削除（＝初期化）されるということなので、注意が必要。

もし`destroy`させないためには（＝キャッシュさせるためには）、`keep-alive`タグを使う。

```App.vue
<!-- keep-aliveタグを使ってキャッシュ -->
<keep-alive>
  <component :is='currentComponents'></component>
</keep-alive>
```

ただし、こうすると従来のライフサイクルメソッドの`destroyed()`が使えなくなる。
その代わりに、`deactivated()`と`activated()`が使えるようになる。

`deactivated()`：該当コンポーネントが**表示されなくなった時**にコールされる
`activated()`：該当コンポーネントが**表示された時**にコールされる

```Parent.vue
<template>
  <div>
    <p>親の山田一郎です</p>
  </div>
</template>

<script>
export default {
  // keep-alive用 
  deactivated() {
    console.log("deactivated：非表示になりました")
  },
  // keep-alive用
  activated() {
    console.log("activated：表示になりました")
  }
}
</script>
```

## 8-9. コンポーネントでのv-model（propsとemit）

親コンポーネントにて`v-model`を指定して、子コンポーネントでその値を反映するやり方は、
`props`と`$emit`をを両方使う。

`type`によって、子コンポーネントの`input`タグの書き方が異なる。

### テキストボックス
```App.vue
<template>
  <div>
    <!-- v-modelの指定 -->
    <LastName v-model="defaultMsg"></LastName>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      defaultMsg: 'デフォルトメッセージ'
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```
```LastName.vue
<template>
  <div>
    <p>私は{{name}}です</p>

    <!-- valueにpropsの値を反映させ、 -->
    <!-- $emitにinputメソッドを指定して発火させて親コンポーネントに反映させる -->
    <input
      id="title"
      type="text"
      :value="value"
      @input="$emit('input', $event.target.value)"
    >
    <pre>{{ value }}</pre>
  </div>
</template>

<script>
export default {
  props: {
    // v-modelの受け口として、valueという変数名で、propsを指定する
    value: {
      type: String,
      required: true
    }
  },
  data: function() {
    return {
      name: "山田",
    }
  }
}
</script>
```
*結果*
![aaa20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/af31f0c3-3dcf-39d0-f486-67dbfeb1941b.gif)

### チェックボックス

```App.vue
<template>
  <div>
    <!-- v-modelの指定 -->
    <LastName v-model="defaultCheck"></LastName>
  </div>
</template>

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

export default {
  data: function() {
    return {
      defaultAge: 15,
      defaultCheck: true
    }
  },
  components: {
    LastName: LastName
  }
}
</script>
```
```LastName.vue
<template>
  <div>
    <p>私は{{name}}です</p>

    <!-- チェックボックスのchangeイベントを利用する -->
    <!-- $emitにchangeメソッドを指定して発火させて親コンポーネントに反映させる -->
    <input
      type="checkbox"
      :checked="childChecked"
      @change="childChecked=$event.target.checked"
    >
    <pre>{{ childChecked }}</pre>
  </div>
</template>

<script>
export default {
  props: {
    // valueという変数名で、propsを指定する
    // チェックは、boolean
    value: {
      type: Boolean,
      required: true
    }
  },
  data: function() {
    return {
      name: "山田",
      childChecked: this.value
    }
  }
}
</script>
```
*結果*
![aaa21.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69626/aa98f666-ba2e-6d8f-c104-c7babe9e5101.gif)