【Vue.js】コンポーネント ~作成からデータの受け渡しまで~

##コンポーネントとは
・画面を構成する要素を個々のUIパーツに分割したもの。

・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。

・メリット　→複数の画面で再生できる。コードの見通しがよくなる。

##コンポーネントの作成手順
ここでは一つのjavascriptファイルで記述していく。

・componentを登録する

componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
　※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
　※コンポーネントの登録は、インスタンスの生成よりも前に記述する。


```js:js

Vue.component('user-list', {
　　　　//componentメソッドでcomponentを登録する。
 data(){
　　　　//dataでは常に関数の戻り値でオブジェクトを設定する。
  return {
   users: [
    {id: 1, name: '1ちゃん'},
    {id: 2, name: '2ちゃん'},
    {id: 3, name: '3ちゃん'},
    {id: 4, name: '4ちゃん'},
    {id: 5, name: '5ちゃん'}
   ]
  }
 },
 template:`
  <ul>
   <li v-for="user in users" :key="user.id">
    {{ user.name }}
   </li>
  </ul>
 `
})

const vm = new Vue({
  el: '#app',
})
```

```html:html
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
 <user-list></user-list>
        //登録したuser-listコンポーネントをタグのようにして呼び出すことができる。
</div>
```


##コンポーネントの入れ子構造
Vueインスタンスをルートとして、その下にコンポーネントがつながっている。
コンポーネント内のtemplateで他のコンポーネントを呼び出すこともできる。
　※コンポーネントのtemplate直下には一つの要素しか書けないことに注意。

以下では、use-listコンポーネントのtemplateで&lt;list-title&gt;&lt;/list-title&gt;を呼び出してみる。

```js:js

Vue.component('list-title', {
  template:`
    <h2>ユーザーリスト</h2>
  `
})

Vue.component('user-list', {
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ]
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id">
         {{ user.name }}
       </li>
     </ul>
   </div>
 `
})

const vm = new Vue({
  el: '#app',
})
```


###出力結果

ユーザーリスト

- 1ちゃん
- 2ちゃん
- 3ちゃん
- 4ちゃん
- 5ちゃん

##ローカル登録とグローバル登録

・グローバル登録
→Vueのtemplate上のどこからでも呼び出して使うことができるが、使用していない場合もコンポーネントの読み込みが発生してしまう。

・ローカル登録
→コンポーネント名：コンポーネントのオブジェクト の形式で登録する。Vueインスタンスのcomponentsオプション配下のみで使用する。

ここまでグローバル登録してきたcomponentを、ローカル登録に書き換えてみる。

```js:js

const ListTitle = {
        //オブジェクトの形式に変える
  template:`
    <h2>ユーザーリスト</h2>
  `
}

Vue.component('user-list', {
  components: {
    'list-title': ListTitle
　　　　　//user-listコンポーネントのcomponents内でローカル登録する
  },
```

ローカル登録の場合は登録したVueインスタンスのcomponentsオプション配下のみで使用できるので、以下のようにマウント先で呼び出しても機能しない。

```html:html

<div id="app">
  <list-title></list-title>
  <user-list></user-list>
</div>
```

##親コンポーネントから子コンポーネントへデータを渡す
コンポーネント内に定義したdataは、そのままでは他のコンポーネントから参照したり書き換えることができない。
そこでpropsを使えば、親から子への単一方向のデータの受け渡しが可能になる。

例として、「親コンポーネント：UserList」から「子コンポーネント：UserDetail」へデータを受け渡してみる。

・子コンポーネントを作成

```js:js

const UserDetail = {
  props: {
    user: {
      type: Object
    }
        //Object型のuserというプロパティを親コンポーネントから受け取る
　　　　 //親コンポーネントでUserDetailコンポーネントを呼び出す際、
        //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している
  },
  template:`
    <div>
      <h2>選択中のユーザー</h2>
      {{ user.name }}
　　　　　　//propsもdataと同様にアクセスできる
    </div>
  `
}
```

・親コンポーネントを編集

```js:js

const UserList = {
  components: {
    'list-title': ListTitle,
    'user-detail': UserDetail
          //上で作成したUserDetailをローカル登録する
  },
  data(){
    return {
      users: [
        {id: 1, name: '1ちゃん'},
        {id: 2, name: '2ちゃん'},
        {id: 3, name: '3ちゃん'},
        {id: 4, name: '4ちゃん'},
        {id: 5, name: '5ちゃん'}
      ],
      selected_user: {}
　　　　　　//クリックイベントで選択中のユーザーを受け取るdataを定義し、
　　　　　　//デフォルトでは空のオブジェクトを入れておく
    }
 },
 template:`
   <div>
     <list-title></list-title>
     <ul>
       <li v-for="user in users" :key="user.id" @click='selected_user = user'>
　　　　　　   //selected_userにクリックされたユーザーを格納する
         {{ user.name }}
       </li>
     </ul>
     <user-detail :user='selected_user'></user-detail>
　　　　　　　 //クリックされたユーザーをv-bindでuser-detailへ受け渡す
   </div>
 `
}
```

このように、親コンポーネントのv-bindで渡したデータが、子コンポーネントのpropsに渡る。

##propsの注意点
・v-bindの名前とpropsの名前は一致する必要がある。

・命名の規則
　　　v-bind：ケバブケースで記述（user-name）
　　　props：キャメルケースで記述（userName）

・propsで受け渡されたオブジェクト自体は直接書き換えることができないが、オブジェクト内のプロパティを書き換えることはできる。
　　　例：propsにuserというオブジェクトが定義されている場合。
　　　　→user.name = '名前' のようにuserのプロパティへの代入はエラーにならないが、
　　　　　user = {} のようにuser自体への代入はエラーになる。


##子コンポーネントから親コンポーネントへデータを渡す

例として、子コンポーネントでユーザー名を編集・登録し、親コンポーネントでそのユーザー名を表示するフォームを作ってみる。

・親となるVueインスタンス

```js:js

const vm = new Vue({
  el: '#app',
  components: {
    'user-detail': UserDetail
  }
})
```

・HTML上で、Vueインスタンスにローカル登録されたUserDetailコンポーネントを表示する。

```html:html

<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <user-detail></user-detail>
</div>
```

・親コンポーネント：UserDetail
user_nameというdataを持ち、templateで呼び出している。
またローカル登録したuser-formコンポーネントをtemplateで呼び出し、user_nameをv-bindで渡している。

```js:js

const UserDetail = {
  components: {
    'user-form' : UserForm
            //このあと定義する子コンポーネントUserFormをローカル登録する
  },
  data(){
    return {
      user_name: 'サトウ　ハナコ'
    }
 },
 template:`
   <div>
     <div>
       <span>ユーザー名： {{ user_name }} </span>
     </div>
     <div>
       <user-form :user-name='user_name' @update:user-name='user_name = $event'></user-form>
            //user-formコンポーネントには、v-bindでuser_nameを渡す
            //このあと定義するupdateメソッド
     </div>
   </div>
 `
}
```

・子コンポーネント：UserForm

```js:js

const UserForm = {
  template:`
    <div>
      <div>ユーザー名変更フォーム</div>
      <input v-model='user_name' />
      <button @click='update'>名前変更</button>
    </div>
  `,
　　　　　　//user_nameを編集するinputタグと、確定するボタンを設置
  props:{
    userName: { type: String, required: true }
  },
  data(){
    return {
      user_name: this.userName
    }
　　　　　　//user_nameを編集するため、propsをdataに設定する
  },
  methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
　　　　　　//templateで設置したボタンがクリックされると、updateメソッドが呼び出される
　　　　　　//$emitメソッドで親にデータを渡す
  }
}
```

$emitメソッド：親コンポーネントへデータを渡す際に使う。
・第一引数…v-onのイベント名（ここでは親コンポーネントで設定した@update:user-nameのイベントハンドラが実行される）
・第二引数…親コンポーネントに渡す値

ここで親コンポーネント「UserDatail」を見てみる。

```
<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>
```
この＄eventには、$emitの第二引数にしていしたthis.user_nameが格納される。

これで親コンポーネントによりユーザー名が表示され、子コンポーネントによりユーザー名変更フォームが表示される。
フォームにテキストを入力してボタンをクリックすると、親コンポーネントによるユーザー名の表示も変更されるようになる。


##sync修飾子を使ってデータを渡す


ここまでで書いたコードを、sync修飾子を使って書き換えてみる。

sync修飾子は親コンポーネントのv-bind属性に指定する。

・親コンポーネント「UserDatail」のv-bindが使われている部分を編集する。
sync修飾子をつけることで、@以下を記述する必要がなくなる。

```
変更前
<user-form :user-name='user_name' @update:user-name='user_name = $event'>
</user-form>

変更後
<user-form :user-name.sync='user_name'>
</user-form>
```

・子コンポーネント「UserForm」の$emitの呼び出し部分を編集する。
$emitの第一引数を'メソッド名: propsの名前'とする。

```
変更前
methods: {
    update(){
      this.$emit('update:user-name', this.user_name)
    }
}

変更後
methods: {
    update(){
      this.$emit('update:userName', this.user_name)
    }
}
```

これでコードを実行すると、同じように動作する。


##スロットの使い方

slotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。

以下の例で、使い方を見てみる。

・js
header・main・footerのそれぞれにslotタグが使われており、layoutコンポーネントを使用する側から、この3つのslotにコンテンツを挿入することができる。

```js:js

const Layout = {
  template:`
    <div class ="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `
}

const vm = new Vue({
  el: '#app',
  components: {
    'layout': Layout
  }
})
```


・html
layoutタグでlayoutコンポーネントを呼び出し、slotに挿入するコンテンツを記述する。

```html:html
<!DOCTYPE html>
<script src="https://unpkg.com/vue@2.5.21"></script>

<div id="app">
  <layout>
    <template slot='header'>
　　　　　　　//name="header"と指定されているslotタグの位置に挿入される
      Header
    </template>
    Main
    コンテンツ
　　　　　　　//slot属性を指定せずに記述した場合、
　　　　　　　　layoutコンポーネントの名前なしslotの位置に挿入される
    <span slot='footer'>
　　　　　　　//spanのような通常のhtml要素にslot属性を指定した場合は、
　　　　　　　　span要素自体がslotの位置に挿入される
      Footer
    </span>
  </layout>
</div>
```