[Express+Vue 搭建電商網站] 15 使用 Vuex Getters 複用資料邏輯
使用 Express + Vue 搭建一個電商網站 - 使用 Vuex Getters 複用資料邏輯
有時候我們需要 computed
store 中的 state,在每個組件中複製貼上同樣的 computed
似乎不是一個明智的作法
Vuex Getter
是 Vuex 提供讓我們可以對 Vuex store 中 state 資料做預處理的方法,就可以達成這個目的
建立 Getter
首先在原本的 src/store/index.js
檔案裡加入一些新的 action
、mutation
屬性
以及這次要使用的 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_ID
、PRODUCT_BY_ID_SUCCESS
用來管理單一商品的資訊狀態 - actions:增加了
productById
來呼叫mutations
中的方法取得商品資訊 - getters:建立 getters,並且加入
allProducts
和productById
方法
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