Vue.jsのコンポーネントの基本を勉強する

# コンポーネントの基本
コンポーネントは名前付きの再利用可能なVueインスタンスです。
コンポーネントを**new Vue**で作成されたルートVueインスタンス内でカスタム要素として使用できる。
自分だけの要素を作れると考えてください。

```html
<div id="example">
  <button-counter></button-counter>
</div>
```

```javascript
// Vueコンポーネント作成
Vue.component(
  'button-counter',
  {
    data() {
      return {
        count:0
      }
    },
    template: '<button @click="count++">カウントアップ：{{ count }}</button>'
  }
)

// Vueインスタンス作成
let app = new Vue({
  el: '#example',
})
```

上記の例では**button-counter**という要素名（コンポーネント名）を登録し、その下括弧（{}）で括られた部分はVue.jsの処理を記述しています。
**template**オプションには名前の通りテンプレート構文を記述します。

Vue.componentはVueインスタンスなので同様のライフサイクルフックなどのオプションを使用できますが、例外として**el**のようなルート固有のオプションは使用できません。

new Vueで作成したインスタンスのルートVueはidが**example**の要素ですので、この要素の内でコンポーネント要素を記述します。

## コンポーネントの再利用

```html
<div id="example">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
```

これらの要素はそれぞれが別々のインスタンスとして作成されるので**count**などのプロパティも独自で保持します。

### **data**は関数でなければならない

各インスタンスがdataプロパティの独立性を保つためです。

Vueインスタンスの場合

```javascript
data:{
  count:0
},
```

Vueコンポーネントの場合

```javascript
data() {
  return {
    count:0
  }
},

// 別の書き方
data: function () {
  return {
    count: 0
  }
}
```

## コンポーネントの編成

![components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398252/ee445aeb-7af6-95c4-6fe2-6d20aa5cd302.png)


例えば、ヘッダー、サイドバー、およびコンテンツ領域のコンポーネントがあり、それぞれには一般にナビゲーションリンク、ブログ投稿などの他のコンポーネントが含まれます。

コンポーネントをテンプレート構文内で使用するにはVue.jsがそれらを認識できるように登録する必要があります。
コンポーネント登録には、**グローバル**と**ローカル**の二種類があります。
これまでの例では**Vue.component**を使用してコンポーネントをグローバルに登録していました。

```javascript
Vue.component(
  'button-counter',
  {
    // コンポーネントの内容
  }
)
```

グローバルに登録されたコンポーネントは、その後に作成されたルートVueインスタンス(new Vue)のテンプレー構文内で使用できます。
さらに、そのVueインスタンスのコンポーネントツリーの全てのサブコンポーネント内でも使用できる。

## プロパティを使用した子コンポーネントへのデータの受け渡し
特定のデータをコンポーネントに渡すことができない限り、そのコンポーネントは役に立ちません。
プロパティ(props)はここで役に立ちます。

プロパティ(props)はコンポーネントに登録できるカスタム属性です。
値が渡されることでそのコンポーネントインスタンスのプロパティになります。
propsオプションを使用して、コンポーネントが受け入れるプロパティのリストに登録することができる。

```html
<div id="example">
  <message-div message= "コンポーネントインスタンス１" ></message-div>
  <message-div message= "コンポーネントインスタンス２" ></message-div>
  <message-div message= "コンポーネントインスタンス３" ></message-div>
</div>
```

```javascript
Vue.component(
  'message-div',
  {
    props: ['message'],
    template: '<p>{{ message }}</p>'
  }
)

let app = new Vue({
  el: '#example',
})
```

通常のアプリケーションでは配列などで可変的にインスタンスを生成したいことがあると思います。

**v-for**を使って動的にプロパティを渡しコンポーネントのインスタンスを生成することができます。

```html
<div id="example">
  <post-message
    v-for="post in posts"
    :key="post.id"
    :message="post.message"
  ></post-message>
</div>
```

```javascript
Vue.component(
  'post-message',
  {
    props: ['message'],
    template: '<p>{{ message }}</p>'
  }
)

let app = new Vue({
  el: '#example',
  data: {
    posts: [
      {id:1, message: 'コンポーネントインスタンス１'},
      {id:2, message: 'コンポーネントインスタンス2'},
      {id:3, message: 'コンポーネントインスタンス3'}
    ]
  }
})
```

## 単一のルート要素
コンポーネントを構築するとき、テンプレート構文には最終的に１要素以上のものが欲しくなります。
そういうときは親要素をつくり子要素として複数の要素を記述します。
またそれぞれの情報ごとにプロパティを定義してしまうと、とてもごちゃごちゃすると思います。
そういう時は単一のプロパティ（配列やオブジェクトのdataプロパティ）

```html
<div id="example">
  <post-message
    v-for="post in posts"
    :key="post.id"
    :post="post"
  ></post-message>
</div>
```

```javascript
Vue.component(
  'post-message',
  {
    props: ['post'],
    template: '<div><p>メッセージID: {{ post.id }}</p><p>メッセージ:{{ post.message }}</p></div>'
  }
)

let app = new Vue({
  el: '#example',
  data: {
    posts: [
      {id:1, message: 'コンポーネントインスタンス１'},
      {id:2, message: 'コンポーネントインスタンス2'},
      {id:3, message: 'コンポーネントインスタンス3'}
    ]
  }
})
```

上記の例では新しいプロパティがpostオブジェクトに追加される際にはいつでも、< post-message > 内で自動的に利用可能になるのです。

## 子コンポーネントのイベントを購読する
コンポーネントを開発する際、親コンポーネントとやろとりする機能が必要になるかもしれません。
親コンポーネントは**v-on**を使って子コンポーネントで起きた任意のイベントを購買することができます。

```html
<div id="example"　:style="{ fontSize: postFontSize + 'em' }">
  <p>親要素</p>
  <post-message
    v-for="post in posts"
    :key="post.id"
    :post="post"
    @size="postFontSize += 0.1"
  ></post-message>
</div>
```

```javascript
Vue.component(
  'post-message',
  {
    props: ['post'],
    template: '\
      <div>\
        <p>{{ post.title }}</p><p>メッセージ:{{ post.message }}</p>\
        <button @click="$emit(\'size\')">コンポーネント全ての文字拡大</button>\
      </div>\
    '
  }
)

let app = new Vue({
  el: '#example',
  data: {
    postFontSize: 1,
    posts: [
      {id:1,title:'子要素', message: 'コンポーネント'}
    ]
  }
})
```

上記の例では、子コンポーネントでページ全体のフォントを大きくするボタンを用意し、そのボタンが押下されたとき、< div id="example"　:style="{ fontSize: postFontSize + 'em' }" >要素の中全体が大きくなるようにします。
**v-on**で渡したメソッド(@size)を子コンポーネントは$emitメソッドで受け取りメソッドを実行することができます。

### イベントと値を送出する
イベントを特定の値付きで送出すると便利なことがあります。
上記の例では親がフォントの拡大率を決めていましたが、子コンポーネントが$emitの２番目のパラメータを使って値を送出することができる。
親コンポーネントでイベントを起動を確認すると、送出された$emitの２番目のパラメータは **$event**でアクセスできます。

```html
<div id="example"　:style="{ fontSize: postFontSize + 'em' }">
  <p>親要素</p>
  <post-message
    v-for="post in posts"
    :key="post.id"
    :post="post"
    @size="postFontSize += $event"
  ></post-message>
</div>
```

```javascript
Vue.component(
  'post-message',
  {
    props: ['post'],
    template: '\
      <div>\
        <p>{{ post.title }}</p><p>メッセージ:{{ post.message }}</p>\
        <button @click="$emit(\'size\', 0.1)">コンポーネント全ての文字拡大</button>\
      </div>\
    '
  }
)

let app = new Vue({
  el: '#example',
  data: {
    postFontSize: 1,
    posts: [
      {id:1,title:'子要素', message: 'コンポーネント'}
    ]
  }
})
```

イベントハンドラがメソッドの場合、送出された$emitの２番目のパラメータはそのメソッドの最初のパラメータとして渡されます。

```html
<div id="example"　:style="{ fontSize: postFontSize + 'em' }">
  <p>親要素</p>
  <post-message
    v-for="post in posts"
    :key="post.id"
    :post="post"
    @size="sizeUp"
  ></post-message>
</div>
```

```javascript
Vue.component(
  'post-message',
  {
    props: ['post'],
    template: `
      <div>
        <p>{{ post.title }}</p><p>メッセージ:{{ post.message }}</p>
        <button @click="$emit(\'size\', 0.1)">コンポーネント全ての文字拡大</button>
      </div>
    `
  }
)

let app = new Vue({
  el: '#example',
  data: {
    postFontSize: 1,
    posts: [
      {id:1,title:'子要素', message: 'コンポーネント'}
    ]
  },
  methods: {
    sizeUp: function(event){
      this.postFontSize += event
    }
  },
})
```

### コンポーネントで**v-model**を使う
[カスタムイベント](https://jp.vuejs.org/v2/guide/components-custom-events.html)は**v-model**で動作するカスタム入力を作成することもできます。
コンポーネント内の < input > は下記のようでなければなりません。

+ **value**属性を**value**プロパティにバインドする
+ **input**では、新しい値で独自のカスタム**input**イベントを送出する

```html
<div id="example">
  <p>入力フォーム</p>
  <custom-input v-model="searchText"></custom-input>
  <p>{{ searchText }}</p>
</div>
```

```javascript
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

let app = new Vue({
  el: '#example',
  data() {
    return {
      searchText: ''
    }
  },
})
```

### スロットによるコンテンツ配信
コンポーネントにコンテンツを渡すことができます。

[スロットガイド](https://jp.vuejs.org/v2/guide/components-slots.html)

```html
<div id="example">
  <p>入力フォーム</p>
  <custom-message
    :title="title"
  >
    <custom-input v-model="searchText"></custom-input>
  </custom-message>
</div>
```

```javascript
Vue.component('custom-message', {
  props: ['title'],
  template: `
    <div>
      <p> {{ title }} </p>
      <slot></slot>
    </div>
  `
})

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

let app = new Vue({
  el: '#example',
  data() {
    return {
      title: '子要素',
      searchText: ''
    }
  },
})
```

上記の例では< custom-message >コンポーネントの中で< custom-input >コンポーネントを記述しています。
< custom-input >コンポーネントは< custom-message >コンポーネントのslot要素に渡されます。
あたかも< custom-message >コンポーネントが< custom-input >コンポーネントを呼び出しているように見えますが、宣言しているのは親である**app**ですので、< custom-input >コンポーネントのイベントや値のやりとりは親と行われます。

## 動的なコンポーネント
コンポーネントを動的に切り替えることができる。
Vueの< component >要素と 特別な属性の**is**で実装可能です。

```html
<div id="example">
  <component :is="componentId"></component>
  <button @click="conversion('custom-message1')">コンポーネント切り替え１</button>
  <button @click="conversion('custom-message2')">コンポーネント切り替え２</button>
  <button @click="conversion('custom-message3')">コンポーネント切り替え３</button>
</div>
```

```javascript
Vue.component('custom-message1', {
  template: `
    <div>
      <p> コンポーネント１ </p>
    </div>
  `
})

Vue.component('custom-message2', {
  template: `
    <div>
      <p> コンポーネント2 </p>
    </div>
  `
})

Vue.component('custom-message3', {
  template: `
    <div>
      <p> コンポーネント3 </p>
    </div>
  `
})


let app = new Vue({
  el: '#example',
  data() {
    return {
      componentId: 'custom-message1'
    }
  },
  methods: {
    conversion:function(params) {
      this.componentId = params
    }
  },
})
```

上記の例では**is**で指定したcomponentIdがそれのボタンによってconversionメソッドに引数をコンポーネントとして呼び出しconversionメソッドはcomponentIdに押下されたボタンに対応したコンポーネントが入り、コンポーネントが切り替わります。

## あとがき
今回で一区切りなので一旦細かく書く記事は終了したいと思います。
記載しなかった題目

[DOM テンプレートパース時の警告](https://jp.vuejs.org/v2/guide/components#DOM-%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%83%91%E3%83%BC%E3%82%B9%E6%99%82%E3%81%AE%E8%AD%A6%E5%91%8A)

## 参考資料
[Vue公式リファレンス](https://jp.vuejs.org/v2/guide/components.html)