公式ホームページのIntro to Vue 3クラス9 (Components & Props)

# 9. Components & Props

>クラス https://www.vuemastery.com/courses/intro-to-vue-3/components-and-props-vue3

VS Codeから該当フォルダを選択して、開きます。その後に、配下のgit部分をクリックして、ブランチをL9-startへ入れ替えてください。

>ブランチ切り替え方法 https://qiita.com/phjg81/items/e6695eb2334d9297a318

今日のポイントはproduct componentを利用するように変更することです。

## Components
WEBは色んなエレメントにて、階層をもって、下記のように構成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/8741867b-cfd0-366b-00b6-98e57c47d9f5.png)
色んな子エレメントが重ねて、親エレメントになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/6e3039ae-fc8e-fe18-cd23-b6502dbbfe4d.png)

まずは、フォルダを作成して、componentsと言う名前にしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/f644ad58-78d4-bad4-4b2a-ebe008257f49.png)

componentsフォルダをマウスの右を押下して、「New File」を選択して、「ProductDisplay.js」にしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/e092a77c-0b3f-3fcb-f486-05eca0771585.png)

その後に内容は下記のものを入れてください。

```javascript:ProductDisplay.js
app.component('product-display', {
    template: 
      /*index.htmlにある内容の中で、product-display以下をコピーして、貼り付けてください。*/ 
      `<div class="product-display">
        <div class="product-container">
          <div class="product-image">
            <img v-bind:src="image">
          </div>
          <div class="product-info">
            <h1>{{ title }}</h1>
    
            <p v-if="inStock">In Stock</p>
            <p v-else>Out of Stock</p>
    
            <div 
              v-for="(variant, index) in variants" 
              :key="variant.id" 
              @mouseover="updateVariant(index)" 
              class="color-circle" 
              :style="{ backgroundColor: variant.color }">
            </div>
            
            <button 
              class="button" 
              :class="{ disabledButton: !inStock }" 
              :disabled="!inStock" 
              v-on:click="addToCart">
              Add to Cart
            </button>
          </div>
        </div>
      </div>`,
     /*main.jsにある内容の中で、createApp({...}) の配下にある内容をコピーして、貼り付けてください。*/ 
     /*cartはサイト全体に表示される内容なので、ここからは削除します。*/ 
    data() {
      return {
          product: 'Socks',
          brand: 'Vue Mastery',
          selectedVariant: 0,
          details: ['50% cotton', '30% wool', '20% polyester'],
          variants: [
            { id: 2234, color: 'green', image: './assets/images/socks_green.jpg', quantity: 50 },
            { id: 2235, color: 'blue', image: './assets/images/socks_blue.jpg', quantity: 0 },
          ]
      }
    },
    methods: {
        addToCart() {
            this.cart += 1
        },
        updateVariant(index) {
            this.selectedVariant = index
        }
    },
    computed: {
        title() {
            return this.brand + ' ' + this.product
        },
        image() {
            return this.variants[this.selectedVariant].image
        },
        inStock() {
            return this.variants[this.selectedVariant].quantity
        }
    }
  })
```

```javascript:main.js：データのcart除いた内容は削除します。
const app = Vue.createApp({
    data() {
      return {
        cart: 0
      }
    },
    methods: {}
})

```

```html:index.html：divのproduct-displayを削除して、product-displayを追記、Componentsをインポート
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Mastery</title>
    <!-- Import Styles -->
    <link rel="stylesheet" href="./assets/styles.css" />
    <!-- Import Vue.js -->
    <script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app">
      <div class="nav-bar"></div>

      <div class="cart">Cart({{ cart }})</div>
      <product-display></product-display>
      <product-display></product-display>
      <product-display></product-display>
    </div>

    <!-- Import App -->
    <script src="./main.js"></script>

    <!-- Import Components -->
    <script src="./components/ProductDisplay.js"></script>

    <!-- Mount App -->
    <script>
      const mountedApp = app.mount('#app')
    </script>

  </body>
</html>

```

これによって、商品エレメントが複数表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/7d80e4e9-6818-3503-c179-0098995e306c.png)

## Props
親エレメントから子エレメントとのやり取りする属性が必要な場合は、Propsを利用すれば、大丈夫です。
利用者がpremiumの場合には、送料が無料になる機能を追加して見ます。

```javascript:main.js：premium属性追加
const app = Vue.createApp({
    data() {
      return {
        cart: 0,
        premium: true
      }
    },
    methods: {}
})

```

```javascript:ProductDisplay.js：propsにpremium属性追加
app.component('product-display', {
    props: {
      premium: {
        type: Boolean,
        required: true
      }
    },
    template: 
    `<div class="product-display">
      ...
      </div>`,
    data() {
      return {
          ...
      }
    },
    methods: {
        ...
    },
    computed: {
        ...
    }
  })
```

これで、premiumを利用するための準備が終わりましたので、使ってみます。

```html:index.html：premiumを追記
      <product-display :premium="premium"></product-display>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/32da63f7-6b5f-643d-5833-fd6eb6d78154.png)

```javascript:ProductDisplay.js：templateへShipping追加とcomputedへshipping追加
app.component('product-display', {
    props: {
      premium: {
        type: Boolean,
        required: true
      }
    },
    template: 
    `<div class="product-display">
      ...
          <p v-else>Out of Stock</p>
          <p>Shipping: {{ shipping }}</p>
      ...
      </div>`,
    data() {
      return {
          ...
      }
    },
    methods: {
        ...
    },
    computed: {
        ...,
        shipping() {
          if (this.premium) {
            return 'Free'
          }
            return 2.99
        }
    }
  })
```
一番上位の<product-display>のみに:premiumを伝えた結果は以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/b4d3b974-9bb4-fccc-314b-f70a28a591f9.png)
ここまでやって見て、リストとして、繰り返されるものや小さなエレメントをComponentsにして、効率化することにより、実装量が減らせる効果があることに気づいてくれたと思います。
それ以外のものは単純にvueのアプリとして、マウントして効率化させましょう。

今回の課題はproduct-display Componentsからproduct-detailsと言うComponentsを作ってdetailsデータオブジェクトを渡してulタグ内のliとして、繰り返して表示して見ましょう。

<details>
<summary>課題確認</summary><div>

```javascript:ProductDisplay.js：templateへproduct-details追加
app.component('product-display', {
    props: {
      ...
    },
    template: 
    /*html*/
    `...
        <div class="product-info">
          ...
          <p>Shipping: {{ shipping }}</p>
          <!-- solution -->
          <product-details :details="details"></product-details>
          ...
    </div>`,
    data() {
      return {
          ...
          details: ['50% cotton', '30% wool', '20% polyester'],
          ...
      }
    },
    methods: {
        ...
    },
    computed: {
        ...
    }
  })

```

```javascript:ProductDetails.js：product-details追加
app.component('product-details', {
    props: {
        details: {
        type: Array,
        required: true
      }
    },
    template: 
    /*html*/
    `
    <ul>
        <li v-for="detail in details">{{ detail }}</li>
    </ul>
    `
  })

```
親componentsが先に来るようにしてください。

```html:index.html：ProductDetailsを追記
    <!-- Import Components -->
    <script src="./components/ProductDisplay.js"></script>
    <!-- solution -->
    <script src="./components/ProductDetails.js"></script>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516328/35a8ca5f-27b0-ab2b-113f-dffc1ef1be95.png)

</div>
</details>