---
# System prepended metadata

title: Vue.js ecshop 專案 6.props & emit 資料傳遞
tags: [ecshop 專案, "Vue.js\_"]

---

> 此篇會分享 **後台產品列表頁面** 在新增商品時，該如何抓取列表的商品資料；以及新增後又該如何更新到產品列表中。
> 其中會使用到 **Vue 資料傳遞**的概念，故會提及 **資料向內傳遞 `Props`** 以及 **向外傳遞 `Emit`** 的用法，那我們就開始吧！

如圖所示，當內、外層元件進行資料傳遞時，就會需要用到 `Props` & `Emit` 來進行溝通。
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/props___emit.png?alt=media&token=9fac5309-bdb8-438d-9572-9ee528e6fa56">

### 資料向內傳遞 Props
* 可以在內層元件透過 `props` 宣告要從外層元件傳遞資料的屬性名稱，並且搭配 `v-bind(:)` 做處理
* 口訣：**前內後外**
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/props.png?alt=media&token=5a5dbce2-531e-4df8-981a-ccd959b5a3b7">
* **單向數據流**
  * 外部定義的資料，往內層傳遞時是單向性的，**無法透過 `v-modal` 來改變 `props` 傳入的內容**
* **命名限制**
  * JS 命名需要使用 駝峰命名法 `superUrl`
  * 但是 HTML 的標籤都須以**小寫**顯示
  * 故在 `<template></template>` 中可以透過 `super-url` 來做調整

### 資料向外傳遞 Emit
#### 觸發外部事件
* 可以在內層元件透過 emit 宣告要觸發外層元件傳遞資料的屬性名稱，並且搭配 `v-on(@)` 做處理
* 口訣：前內後外
  * 在內層的 **刪除按鈕** 透過 emit 觸發外部事件 `@click="$emit('del-item')"`
  * 在外層的 `<DelModal></DelModal>` 加入 `@del-item="delProduct"` 以利觸發外層 `delProduct（)` 事件，進而刪除商品。
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/emit_demo.png?alt=media&token=d12d081f-9f78-43ab-b117-da88bca3e0b7">
#### 傳遞資料狀態
* 將內部的資料傳到外部，函式需要帶入參數
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/emit_demo2.png?alt=media&token=f53f3577-2c05-4f5a-98ea-5d0d44f4b1ff">

### 透過彈出視窗 Modal 新增商品
* 首先回到 ***ProductModal.vue*** 製作建立商品模板
 <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/addProducts_modal.png?alt=media&token=79ae6169-9055-4133-a224-7cc5997fd176">
* 接著在 ***ProductsPage.vue*** 新增以下資料：
  * 於 `data` 新增 `tempProduct: {}`
  * 於 `methods` 新增 `openModal()`、`updateProduct()`
    * **打 api 時要帶入參數 `this.axios.post(api, { data: this.tempProduct })`**
  ```javascript=
  <script>
  import ProductModal from '../components/ProductModal.vue'

  export default {
    data () {
      return {
        products: [],
        pagination: {},
        tempProduct: {}
      }
    },
    components: {
      ProductModal
    },
    methods: {
      getProducts () {
        const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/products`
        this.axios.get(api)
          .then((res) => {
            if (res.data.success) {
              console.log(res.data)
              this.products = res.data.products
              this.pagination = res.data.pagination
            }
          })
      },
      // 開啟 modal
      openModal () {
        this.tempProduct = {}
        const productComponent = this.$refs.productModal
        productComponent.showModal()
      },
      // 新增商品
      updateProduct (item) {
        this.tempProduct = item
        const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product`
        const productComponent = this.$refs.productModal
        this.axios.post(api, { data: this.tempProduct })
          .then((res) => {
            console.log(res)
            // 完成後關閉 modal
            productComponent.hideModal()
            // 再次更新商品列表
            this.getProducts()
          })
      }
    },
    created () {
      this.getProducts()
    }
  }
  </script>
  ```
* 完成後可以新增商品做確認
  * 商品新增**成功**
   <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/add_success.png?alt=media&token=17c9d168-89a1-4e55-a562-87323046f6ac">
  * 商品新增**失敗**：（可能是必填欄位沒填，可以確認 message）
   <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/add_false.png?alt=media&token=76cd6c36-d44c-44be-adba-5b7e806323c7">
   
   
### 編輯、更新商品
* 在 ***ProductsPage.vue*** 新增以下：
  * 在 `data` 新增 `isNew: false`
  * 將 `openModal()` 新增參數： `openModal(isNew, item)`
    * 針對新增商品與編輯商品做區隔：
      * 如果是 **新增商品** 就帶出空物件
      * **編輯商品** 就抓 item 的資料
    ```javascript=
    openModal (isNew, item) {
      //  如果是新的就帶出空物件，不是就抓 item 的資料
      if (isNew) {
        this.tempProduct = {}
      } else {
        this.tempProduct = { ...item }
      }
      this.isNew = isNew
      const productComponent = this.$refs.productModal
      productComponent.showModal()
    }
      ```
  * 將 **`增加一個產品`** 按鈕帶入參數 `@click="openModal(true)`
    ```html=
    <button class="btn btn-primary" type="button" 
            @click="openModal(true)">增加一個產品</button>
    ```
  * 回到 **`編輯`** 按鈕新增觸發事件 `@click="openModal(false, item)"`
    ```html=
    <button class="btn btn-outline-primary btn-sm" 
            @click="openModal(false, item)">編輯</button>
    ```
* 調整 `updateProduct()` 函式，分為 **新增商品**、**編輯商品**
  ```javascript=
  updateProduct (item) {
    this.tempProduct = item
    // 新增
    let api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product`
    let httpMethod = 'post'

    // 編輯
    if (!this.isNew) {
      api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product/${item.id}`
      httpMethod = 'put'
    }
    const productComponent = this.$refs.productModal
    this.axios[httpMethod](api, { data: this.tempProduct })
      .then((res) => {
        console.log(res)
        productComponent.hideModal()
        this.getProducts()
      })
  }
  ```
* 回到 ***ProductModal.vue*** 新增以下
  * 宣告 `props`
    ```javascript=
    props: {
        product: {
          type: Object,
          default () { return {} }
        }
      },
      watch: {
        product () {
          this.tempProduct = this.product
        }
      },
    ```
  * 針對 **`確認`** 按鈕新增 `emit` 觸發外部事件
    ```html=
    <button type="button" class="btn btn-primary"
            @click="$emit('update-product', tempProduct)">確認</button>
    ```
* 回到 ***ProductsPage.vue*** 針對 `<ProductModal></ProductModal>` 新增 `ref` & `props` & `emit` :
  ```html=
  <ProductModal ref="productModal"
                :product="tempProduct" 
                @update-product="updateProduct"></ProductModal>
  ```
  <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/editProduct_demo.png?alt=media&token=af335d34-9bb7-42cb-ab66-74d58999d107">

下篇文章將會插播 **使用 mixin 整合相同程式碼** 以利多檔案間做使用，並且接續進行 **刪除商品** 的設定。