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

使用 Vuex Getters 複用資料邏輯

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

成果

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

留言