【Vue.js - 5】コンポーネントでUI部品を作る

## まとめ
 - コンポーネントはJS&HTMLをセットで管理できる
 - 親から子は属性で渡してporpsで受け取る
 - 子から親は子が$emitでカスタムイベントで発火して親が受け取る
 - スロットはテンプレートがカスタマイズできる
 - コンポーネント開発を楽にしたいなら単一ファイルコンポーネントがおすすめ！

## 1. コンポーネントの定義
 
コンポーネントの登録

```html:html
<div id="app">
  <my-component></my-component>
<div>
```
```js:Vue.js
Vue.component('my-component', {
  template: '<p>MyComponent</p>',
  //データはオブジェクトを返す関数にしなければならない
  data: function() { 
    return { message: 'Hello!World' }
  },
  methods: {
  }
})
```

ローカルへの登録（new Vueする前にコンポーネントを定義する）

```js:Vue.js
var myComponent = {
  template: '<p>MyComponent</p>'
}
new Vue({
  el: '#app',
  components: {
    'my-component': myComponent
  }
})
```


## 2. コンポーネント間の通信

コンポーネント間の通信（親から子）（props down）
プロパティとして文字列を渡す

```html:html
<comp-child val="これは子A"></comp-child>
<comp-child val="これは子B"></comp-child>
```

```js:Vue.js
Vue.component('comp-child', {
  //テンプレートで受け取ったvalを使用
  template: '<p>{{ val }}</p>',
  //受け取る属性名を指定
  props: ['val']
})
```

プロパティとしてデータを渡す

```html:html
<comp-child :val="valueA"></comp-child>
<comp-child :val="valueB"></comp-child>
```

```js:Vue.js
new Vue({
  data: {
    valueA: 'これは子A',
    valueB: 'これは子B'
  }
})
```

コンポーネントをリストレンダリング
子コンポーネント

```html:html
<ul>
  <comp-child v-for="item in list"
    v-bind:key="item.id"
    v-bind:name="item.name"
    v-bind:hp="item.hp"></comp-child>
</ul>
```

```js:Vue.js
Vue.component('comp-child', {
  template: '<li>{{ name }} HP.{{ hp }}</li>',
  props: ['name', 'hp']
})
```

親コンポーネント

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: 'スライム', hp: 100 },
      { id: 2, name: 'ゴブリン', hp: 200 },
      { id: 3, name: 'ドラゴン', hp: 500 }
    ]
  }
})
```

エラーになるパターン

```js:Vue.js
Vue.component('comp-child', {
  template: '<li>{{ name }} HP.{{ hp }}
  <button v-on:click="doAttack">攻撃する</button></li>',
  props: ['name', 'hp'],
  methods: {
    doAttack: function () {
      //propsで受け取ったデータは勝手に書き換えてはいけない！
      this.hp -= 10  //-> [Vue warn] error!
    }
  }
})
```

propsの受け取りデータ型を指定する

```js:Vue.js
//タイプチェックを省略した場合
Vue.component('example', {
  props: ['value']  //どんな型も受け入れる
})
//データ型のみチェックする場合
Vue.component('example', {
  props: {
    value: データ型
  }
})
```

インスタンスのチェック

```html:html
<example v-bind:value="value"></example>
```

```js:Vue.js
function Cat(name) {
  this.name = name
}
Vue.component('example', {
  props: {
    value: Cat  //猫データのみ許可！
  }
})
new Vue({
  data: {
    value: new Cat('たま')  //valueは猫データ
  }
})
```

その他のオプションも使用する場合

```js:Vue.js
Vue.component('example', {
  props: {
    value: {
      type: [String, Number], //許可するデータ型
      default: 100, //デフォルト値
      required: true, //必須にする
      validator: function (value) { //カスタムバリデータ関数、チェックして真偽値を返す
        return value > 10
      }
    }
  }
})
```

コンポーネント間の通信（子から親）（event up）
子のイベントを親にキャッチさせる

子コンポーネント

```js:Vue.js
Vue.component('comp-child', {
  //クリックでhandleClickを実行
  template: '<button v-on:click="handleClick">イベント発火</button>',
  methods: {
    //ボタンのクリックイベントのハンドラでchilds-eventを発火する
    handleClick: function () {
      this.$emit('childs-event')
    }
  }
})
```

親コンポーネント

```html:html
<comp-child v-on:childs-event="parentsMethod"></comp-child>
```

```js:Vue.js
new Vue({
  el: '#app',
  methods: {
    //childs-eventが発生した！
    parentsMethod: function () {
      alert('イベントをキャッチ！ ')
    }
  }
})
```

親が持つデータを操作

子コンポーネント

```js:Vue.js
Vue.component('comp-child', {
  template: '<li>{{ name }} HP.{{ hp }}
  <button v-on:click="doAttack">攻撃する</button></li>',
  props: {
    id: Number,
    name: String,
    hp: Number
  },
  methods: {
    //ボタンのクリックイベントのハンドラから$emitでattackを発火する
    doAttack: function () {
      //引数として自分のIDを渡す
      this.$emit('attack', this.id)
    }
  }
})
```

親コンポーネント

```html:html
<ul>
  <comp-child v-for="item in list"
    v-bind:key="item.id"
    v-bind="item"
    v-on:attack="handleAttack"></comp-child>
</ul>
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: 'スライム', hp: 100 },
      { id: 2, name: 'ゴブリン', hp: 200 },
      { id: 3, name: 'ドラゴン', hp: 500 }
    ]
  },
  methods: {
    //attackが発生した！
    handleAttack: function (id) {
      //引数のIDから要素を検索
      var item = this.list.find(function (el) {
        return el.id === id
      })
      //HPが0より多ければ10減らす
      if (item !== undefined && item.hp > 0) item.hp -= 10
    }
  }
})
```

カスタムタグのイベントハンドリング

```html:html
<!-- コンポーネント側からclickを$emitで呼び出さない限り発火しない -->
<my-icon v-on:click="handleClick"></my-icon>
<!-- .native修飾子を使い、元々のイベントを直接発火する -->
<my-icon v-on:click.native="handleClick"></my-icon>
```

コンポーネント間の通信（非親子）

```js:Vue.js

//イベントバス用インスタンスを作成
var bus = new Vue({
  data: {
    count: 0
  }
})
Vue.component('component-b', {
  template: '<p>bus: {{ bus.count }}</p>',
  computed: {
    //busのデータを算出プロパティに使用
    bus: function () {
      return bus.$data
    }
  },
  //createdフックでbus-eventが起きた時の処理をかく
  created: function () {
    bus.$on('bus-event', function () {
      this.count++
    })
  }
})
```

コンポーネント間の通信（その他）
子コンポーネントを参照する$refs

親コンポーネント

```html:html
<comp-child ref="child">
```

```js:Vue.js
new Vue({
  el: '#app',
  methods: {
    handleClick: function () {
      //子コンポーネントのイベントを発火
      this.$refs.child.$emit('open')
    }
  }
})
```

子コンポーネント

```js:Vue.js
Vue.component('comp-child', {
  template: '<div>...</div>',
  created: function () {
    //自分自身のイベント（$onを使ってハンドルできる）
    this.$on('open', function () {
      console.log('なにか処理')
    })
  }
})
```

コンポーネントの属性のスコープ
コンポーネントの属性の値に当たる部分は、親のスコープになる

```html:html
<comp-child v-on:childs-events="親のメソッド">
<comp-child v-on:childs-events="parentsMethod(親のデータ)">
```

子コンポーネントが引数を持って\$emitを実行している場合、
子コンポーネントの引数は\$event変数で使用できる

```html:html
<comp-child v-on:childs-events="parentsMethod($event, parentsData)">
```

```js:Vue.js
new Vue({
  el: '#app',
  data: {
    parentsData: '親のデータ'
  },
  methods: {
    parentsMethod: function(childsArg, parentsArg){
      //処理
    }
  }
})
```

\$event変数は\$emitの第１引数しか持たない
→複数の引数を渡したい場合は、1つのオブジェクトにまとめる

```js:Vue.js
this.$emit('childs-event', { id: 1 name: '新しい名前'})
```