Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

[Express+Vue 搭建電商網站] 15 使用 Vuex Getters 複用資料邏輯

使用 Express + Vue 搭建一個電商網站 - 使用 Vuex Getters 複用資料邏輯

Ray

系列文章 : Express+Vue 搭建電商網站(第 15 / 22 篇) 查看完整系列 →

有時候我們需要 computed store 中的 state,在每個組件中複製貼上同樣的 computed 似乎不是一個明智的作法
Vuex Getter 是 Vuex 提供讓我們可以對 Vuex store 中 state 資料做預處理的方法,就可以達成這個目的

建立 Getter

首先在原本的 src/store/index.js 檔案裡加入一些新的 actionmutation 屬性
以及這次要使用的 getter

import Vue from 'vue'; import Vuex from 'vuex'; import axios from 'axios'; const
API_BASE = 'http://localhost:3000/api/v1'; Vue.use(Vuex); export default new
Vuex.Store({ strict: true, state: { // bought items cart: [], // ajax loader
showLoader: false, // selected product product: {}, // all products products:
[], // all manufacturers manufacturers: [], }, mutations: { ADD_TO_CART(state,
payload) { const { product } = payload; state.cart.push(product) },
REMOVE_FROM_CART(state, payload) { const { productId } = payload state.cart =
state.cart.filter(product => product._id !== productId) }, ALL_PRODUCTS(state) {
state.showLoader = true; }, ALL_PRODUCTS_SUCCESS(state, payload) { const {
products } = payload; state.showLoader = false; state.products = products; },
PRODUCT_BY_ID(state) { state.showLoader = true; }, PRODUCT_BY_ID_SUCCESS(state,
payload) { state.showLoader = false; const { product } = payload; state.product
= product; } }, getters: { allProducts(state) { return state.products; },
productById: (state, getters) => id => { if (getters.allProducts.length > 0) {
return getters.allProducts.filter(p => p._id == id)[0]; } else { return
state.product; } } }, actions: { allProducts({ commit }) {
commit('ALL_PRODUCTS') axios.get(`${API_BASE}/products`).then(response => {
console.log('response', response); commit('ALL_PRODUCTS_SUCCESS', { products:
response.data, }); }) }, productById({ commit }, payload) {
commit('PRODUCT_BY_ID'); const { productId } = payload;
axios.get(`${API_BASE}/products/${productId}`).then(response => {
commit('PRODUCT_BY_ID_SUCCESS', { product: response.data, }); }) } } });

主要增加了三個部分

  • mutations:增加了 PRODUCT_BY_IDPRODUCT_BY_ID_SUCCESS 用來管理單一商品的資訊狀態
  • actions:增加了 productById 來呼叫 mutations中的方法取得商品資訊
  • getters:建立 getters,並且加入 allProductsproductById 方法
    allProducts 取得所有商品;productById 則會回傳指定 id 的商品資料,如果商品不存在則回傳空的物件

在後台 Products 組件中使用 Getters

先使用一個簡單的範例說明 Getters 是怎麼運作的,打開 src/views/admin/Products.vue 組件
並且把以下內容

return this.$store.state.products[0];

替換成

return this.$store.getters.allProducts[0];

在這個範例中,我們通過 this.$store.getters.allProducts 來調用 getter 中的 allProducts 屬性
並且顯示出第一個商品的名稱

建立 ProductDetail 組件

在簡單的了解 Getter 是怎麼運作之後,要來實現單一商品詳細內容的組件

新建 src/components/products/ProductDetail.vue

<template>
  <div class="product-details">
    <div class="product-details__image">
      <img :src="product.image" alt class="image" />
    </div>
    <div class="product-details__info">
      <div class="product-details__description">
        <small>生產商{{ product.manufacturer.name }}</small>
        <h3>產品名稱{{ product.name }}</h3>
        <p>簡介{{ product.description }}</p>
      </div>
      <div class="product-details__price-cart">
        <p>價錢{{ product.price }}</p>
        <product-button :product="product"></product-button>
      </div>
    </div>
  </div>
</template>

<style>
.product-details__image .image {
  width: 100px;
  height: 100px;
}
</style>

<script>
import ProductButton from "./ProductButton";
export default {
  props: ["product"],
  components: {
    "product-button": ProductButton,
  },
};
</script>

可以看到這個組件將會利用父組件傳入的 product 物件來顯示內容
並且複用了先前建立的 ProductButton 組件

在 ProductItem 組件中為商品加入連結

有了詳細頁面,我們還需要設定怎麼進入商品詳細頁面的連結
打開 src/components/products/ProductItem.vue 組件,將 <template> 區塊編輯成以下樣式

<template>
  <div>
    <div class="product">
      <router-link :to="'/detail/' + product._id" class="product-link">
        <p class="product__name">商品名稱{{ product.name }}</p>
        <p class="product__description">簡介{{ product.description }}</p>
        <p class="product__price">售價{{ product.price }}</p>
        <p class="product.manufacturer">
          生產商{{ product.manufacturer.name }}
        </p>
        <img :src="product.image" alt class="product__image" />
      </router-link>
      <product-button :product="product"></product-button>
    </div>
  </div>
</template>

<style>
.product {
  border-bottom: 1px solid black;
}

.product__image {
  width: 100px;
  height: 100px;
}
</style>

可以發現我們使用了之前學過的 vue-router 中的 router-link 方法跳轉到指定編號的商品頁面

在 ProductList 中使用 Getters

而原先商品列表組件(src/components/products/ProductList.vue)「取得所有商品」功能
也是直接操作 Vuex store 中的 state

computed: { products() { return this.$store.state.products; } },

在這邊我們也改用剛剛學到的 Getter 改寫成以下內容,使用指定的 getter:allProducts 取得所有商品資料

computed: { products() { return this.$store.getters.allProducts; } },

建立 Detail 頁面組件

現在子組件都完成了,只缺一個詳細商品頁面將組件拿來使用

建立 src/views/Detail.vue

<template>
  <div>
    <product-detail :product="product"></product-detail>
  </div>
</template>

<script>
import ProductDetail from "@/components/products/ProductDetail.vue";
export default {
  created() {
    // 如果使用者儲存的狀態中不存在此商品,則從後端取得商品資訊
    const { name } = this.product;
    if (!name) {
      this.$store.dispatch("productById", {
        productId: this.$route.params["id"],
      });
    }
  },
  computed: {
    product() {
      return this.$store.getters.productById(this.$route.params["id"]);
    },
  },
  components: {
    "product-detail": ProductDetail,
  },
};
</script>

引入了 ProductDetail 組件,並且在生命週期中頁面建立「created()」時檢查使用者端是否有指定商品的資料
沒有則使用異步方法呼叫 Vuex action 取得資訊,並透過 mutation 修改狀態

其中的 computed 屬性用於取得狀態管理中的指定商品
而其中的 id 參數透過 this.$route.params['id'] 取得路由中的產品編號,傳入指定的 getter 取得指定商品資料

設定 Detail 頁面路由

剛剛提到會使用路由傳入的產品編號來查詢產品資料,有沒有想起什麼事情?

打開 vue-router 的設定檔 src/router/index.js 引入 Detail 頁面

import Detail from '@/views/Detail';

在路由規則中加入 Detail 頁面設定值

{ path: '/detail/:id', name: 'Detail', component: Detail, },

成果

打開專案頁面,可以在商品列表發現所有商品現在都有了超連結
點擊超連結之後會進入商品詳細頁面,而顯示的就是該商品的詳細資料


專案範例程式碼 GitHub 網址:ray247k/mini-E-commerce

最新文章

文章分類

Tag