Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

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

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

Ray

有時候我們需要 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

最新文章

Category

Tag