Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

[Express+Vue 搭建電商網站] 17 使用常數管理變數

使用 Express + Vue 搭建一個電商網站 - 使用常數管理變數

Ray

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

很多時候我們會把功能封裝到組件內,以便重複使用組件,而在組件內我們會定義很多方法名稱去呼叫不同的事件
這中間只要有一個字打錯,整個流程就會錯誤,並且非常難除錯,因此我們會使用常數維持一致
打錯字的時候就會報錯,方便除錯

建立 ManufacturerForm 組件

和商品資訊一樣,我們也要將製造商資訊封裝到另一個單獨的組件 ManufacturerForm

新增 src/components/ManufacturerForm.vue 檔案

<template>
  <form @submit.prevent="saveManufacturer">
    <div class="form-group">
      <label>Name</label>
      <input
        type="text"
        placeholder="Name"
        v-model="model.name"
        name="name"
        class="form-control"
      />
    </div>

    <div class="form-group new-button">
      <button class="button">
        <i class="fa fa-pencil"></i>
        <!-- Conditional rendering for input text -->
        <span v-if="isEditing">Update Manufacturer</span>
        <span v-else>Add Manufacturer</span>
      </button>
    </div>
  </form>
</template>

<script>
export default {
  props: ["model", "isEditing"],
  methods: {
    saveManufacturer() {
      this.$emit("save-manufacturer", this.model);
    },
  },
};
</script>

重構 getters 檔案

在建立編輯生產商的組件前,需先加入對應的 getter 屬性

打開 src/store/getters.js 檔案,對 manufacturerGetters 增加一個屬性
用來取得狀態庫中的指定生產商

manufacturerById: (state, getters) => id => { if
(getters.allManufacturers.length > 0) { return
getters.allManufacturers.filter(manufacturer => manufacturer._id === id)[0] }
else { return state.manufacturer; } }

建立 EditManufacturers 頁面

在建立好顯示製造商資訊的表單組件 ManufacturerForm 以及設定好取得資料的 getter 之後
我們要來建立編輯製造商的頁面 src/views/admin/EditManufacturers.vue

<template>
  <manufacturer-form
    @save-manufacturer="addManufacturer"
    :model="model"
    :isEditing="true"
  ></manufacturer-form>
</template>

<script>
import ManufacturerForm from "@/components/ManufacturerForm.vue";
export default {
  created() {
    this.$store.dispatch("manufacturerById", {
      manufacturerId: this.$route.params["id"],
    });
  },
  computed: {
    model() {
      const manufacturer = this.$store.getters.manufacturerById(
        this.$route.params["id"],
      );
      return { ...manufacturer };
    },
  },
  methods: {
    addManufacturer(model) {
      this.$store.dispatch("updateManufacturer", {
        manufacturer: model,
      });
    },
  },
  components: {
    "manufacturer-form": ManufacturerForm,
  },
};
</script>

組件在創建時會使用 action 去異步取得製造商資訊,並透過 mutation 修改狀態池 之所以在 model 這個 computed 中回傳製造商資料的備份資料
是為了在修改資料被送出前不對當前 store 的生產商屬性做操作
而組件內如果對生產商有操作,會透過 action 修改商品資訊,然後呼叫 mutation 變更狀態池

建立 NewManufacturers 頁面

跟剛剛的編輯頁面邏輯差不多,這邊要建立新增頁面 src/views/admin/NewManufacturers.vue

<template>
  <manufacturer-form
    @save-manufacturer="addManufacturer"
    :model="model"
  ></manufacturer-form>
</template>

<script>
import ManufacturerForm from "@/components/ManufacturerForm.vue";
export default {
  computed: {
    model() {
      return {};
    },
  },
  methods: {
    addManufacturer(model) {
      this.$store.dispatch("addManufacturer", {
        manufacturer: model,
      });
    },
  },
  components: {
    "manufacturer-form": ManufacturerForm,
  },
};
</script>

重構 Admin menu

src/views/admin/Index.vue 加入新的頁面用來增加製造商,沒什麼多解釋的

<li>
    <router-link to="/admin/manufacturers/new">新增製造商</router-link>
</li>

增加路由邏輯

頁面跟連結都做好了,打開 src/router/index.js 引入路由設定

import NewManufacturers from '@/views/admin/NewManufacturers'; import
EditManufacturers from '@/views/admin/EditManufacturers';

增加 admin 頁面下路由 children 屬性

{ path: 'manufacturers/new', name: 'NewManufacturers', component:
NewManufacturers, }, { path: 'manufacturers/edit/:id', name:
'EditManufacturers', component: EditManufacturers, },

用常數管理通用名稱

很多時候我們會把功能封裝到組件內,以便重複使用組件
而在組件內我們會定義很多方法名稱去呼叫不同的事件,例如當使用者按下了送出,觸發了 ADD_PRODUCT 事件
那在 action 中就要有相應的事件
並且 action 在後端處理完請求後,也會呼叫指定的 mutations 來處理狀態的變更
這中間只要有一個字打錯,整個流程就會錯誤,並且非常難除錯

因此我們會使用常數的方式定義 actionsmutations 中的事件
只要我們都使用同一組常數就可以維持一致。關鍵的是如果這麼做打錯字的時候就會報錯,方便除錯

接著就來實作建立 src/store/mutation-types.js 檔案作為 mapping 表

export const ALL_PRODUCTS = 'ALL_PRODUCTS'; export const ALL_PRODUCTS_SUCCESS =
'ALL_PRODUCTS_SUCCESS'; export const PRODUCT_BY_ID = 'PRODUCT_BY_ID'; export
const PRODUCT_BY_ID_SUCCESS = 'PRODUCT_BY_ID_SUCCESS'; export const ADD_PRODUCT
= 'ADD_PRODUCT'; export const ADD_PRODUCT_SUCCESS = 'ADD_PRODUCT_SUCCESS';
export const UPDATE_PRODUCT = 'UPDATE_PRODUCT'; export const
UPDATE_PRODUCT_SUCCESS = 'UPDATE_PRODUCT_SUCCESS'; export const REMOVE_PRODUCT =
'REMOVE_PRODUCT'; export const REMOVE_PRODUCT_SUCCESS =
'REMOVE_PRODUCT_SUCCESS'; export const ADD_TO_CART = 'ADD_TO_CART'; export const
REMOVE_FROM_CART = 'REMOVE_FROM_CART'; export const ALL_MANUFACTURERS =
'ALL_MANUFACTURER'; export const ALL_MANUFACTURERS_SUCCESS =
'ALL_MANUFACTURER_S'; export const MANUFACTURER_BY_ID = 'MANUFACTURER_BY_ID';
export const MANUFACTURER_BY_ID_SUCCESS = 'MANUFACTURER_BY_ID_SUCCESS'; export
const ADD_MANUFACTURER = 'ADD_MANUFACTURER'; export const
ADD_MANUFACTURER_SUCCESS = 'ADD_MANUFACTURER_SUCCESS'; export const
UPDATE_MANUFACTURER = 'UPDATE_MANUFACTURER'; export const
UPDATE_MANUFACTURER_SUCCESS = 'UPDATE_MANUFACTURER_SUCCESS'; export const
REMOVE_MANUFACTURER = 'REMOVE_MANUFACTURER'; export const
REMOVE_MANUFACTURER_SUCCESS = 'REMOVE_MANUFACTURER_SUCCESS';

重構 actions 檔案

打開 src/store/actions.js 檔案,引入剛剛的常數表文件後,把其中的方法名稱通通改成使用常數表內的常數

import axios from 'axios'; import { ADD_PRODUCT, ADD_PRODUCT_SUCCESS,
PRODUCT_BY_ID, PRODUCT_BY_ID_SUCCESS, UPDATE_PRODUCT, UPDATE_PRODUCT_SUCCESS,
REMOVE_PRODUCT, REMOVE_PRODUCT_SUCCESS, ALL_PRODUCTS, ALL_PRODUCTS_SUCCESS,
ALL_MANUFACTURERS, ALL_MANUFACTURERS_SUCCESS, MANUFACTURER_BY_ID,
MANUFACTURER_BY_ID_SUCCESS, ADD_MANUFACTURER, ADD_MANUFACTURER_SUCCESS,
UPDATE_MANUFACTURER, UPDATE_MANUFACTURER_SUCCESS, REMOVE_MANUFACTURER,
REMOVE_MANUFACTURER_SUCCESS, } from './mutation-types'; const API_BASE =
'http://localhost:3000/api/v1'; export const productActions = { allProducts({
commit }) { commit(ALL_PRODUCTS) axios.get(`${API_BASE}/products`).then(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, }); }) },
removeProduct({ commit }, payload) { commit(REMOVE_PRODUCT); const { productId }
= payload; axios.delete(`${API_BASE}/products/${productId}`).then(() => { //
回傳 productId用來刪除對應商品 commit(REMOVE_PRODUCT_SUCCESS, { productId, });
}) }, updateProduct({ commit }, payload) { commit(UPDATE_PRODUCT); const {
product } = payload; axios.put(`${API_BASE}/products/${product._id}`,
product).then(() => { commit(UPDATE_PRODUCT_SUCCESS, { product, }); }) },
addProduct({ commit }, payload) { commit(ADD_PRODUCT); const { product } =
payload; axios.post(`${API_BASE}/products`, product).then(response => {
commit(ADD_PRODUCT_SUCCESS, { product: response.data, }) }) } }; export const
manufacturerActions = { allManufacturers({ commit }) {
commit(ALL_MANUFACTURERS); axios.get(`${API_BASE}/manufacturers`).then(response
=> { commit(ALL_MANUFACTURERS_SUCCESS, { manufacturers: response.data, }); }) },
manufacturerById({ commit }, payload) { commit(MANUFACTURER_BY_ID); const {
manufacturerId } = payload;
axios.get(`${API_BASE}/manufacturers/${manufacturerId}`).then(response => {
commit(MANUFACTURER_BY_ID_SUCCESS, { manufacturer: response.data, }); }) },
removeManufacturer({ commit }, payload) { commit(REMOVE_MANUFACTURER); const {
manufacturerId } = payload;
axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => { // 回傳
manufacturerId用來刪除對應的製造商 commit(REMOVE_MANUFACTURER_SUCCESS, {
manufacturerId, }); }) }, updateManufacturer({ commit }, payload) {
commit(UPDATE_MANUFACTURER); const { manufacturer } = payload;
axios.put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer).then(()
=> { commit(UPDATE_MANUFACTURER_SUCCESS, { manufacturer, }); }) },
addManufacturer({ commit }, payload) { commit(ADD_MANUFACTURER); const {
manufacturer } = payload; axios.post(`${API_BASE}/manufacturers`,
manufacturer).then(response => { commit(ADD_MANUFACTURER_SUCCESS, {
manufacturer: response.data, }) }) } }

重構 manufacturer 檔案

src/store/mutations.js 也做跟上一步一樣的事情

import { ADD_PRODUCT, ADD_PRODUCT_SUCCESS, PRODUCT_BY_ID, PRODUCT_BY_ID_SUCCESS,
UPDATE_PRODUCT, UPDATE_PRODUCT_SUCCESS, REMOVE_PRODUCT, REMOVE_PRODUCT_SUCCESS,
ADD_TO_CART, REMOVE_FROM_CART, ALL_PRODUCTS, ALL_PRODUCTS_SUCCESS,
ALL_MANUFACTURERS, ALL_MANUFACTURERS_SUCCESS, MANUFACTURER_BY_ID,
MANUFACTURER_BY_ID_SUCCESS, ADD_MANUFACTURER, ADD_MANUFACTURER_SUCCESS,
UPDATE_MANUFACTURER, UPDATE_MANUFACTURER_SUCCESS, REMOVE_MANUFACTURER,
REMOVE_MANUFACTURER_SUCCESS, } from './mutation-types'; export const
productMutations = { [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;
}, [REMOVE_PRODUCT](state) { state.showLoader = true; },
[REMOVE_PRODUCT_SUCCESS](state, payload) { state.showLoader = false; const {
productId } = payload; state.products = state.products.filter(product =>
product._id !== productId); }, [UPDATE_PRODUCT](state) { state.showLoader =
true; }, [UPDATE_PRODUCT_SUCCESS](state, payload) { state.showLoader = false;
const { product: newProduct } = payload; state.product = newProduct;
state.products = state.products.map(product => { if (product._id ===
newProduct._id) { return newProduct; } return product; }) },
[ADD_PRODUCT](state) { state.showLoader = true; }, [ADD_PRODUCT_SUCCESS](state,
payload) { state.showLoader = false; const { product } = payload; state.products
= state.products.concat(product); }, }; export const cartMutations = {
[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) }, } export const manufacturerMutations = {
[ALL_MANUFACTURERS](state) { state.showLoader = true; },
[ALL_MANUFACTURERS_SUCCESS](state, payload) { const { manufacturers } = payload;
state.showLoader = false; state.manufacturers = manufacturers; },
[MANUFACTURER_BY_ID](state) { state.showLoader = true; },
[MANUFACTURER_BY_ID_SUCCESS](state, payload) { state.showLoader = false; const {
manufacturer } = payload; state.manufacturer = manufacturer; },
[REMOVE_MANUFACTURER](state) { state.showLoader = true; },
[REMOVE_MANUFACTURER_SUCCESS](state, payload) { state.showLoader = false; const
{ manufacturerId } = payload; state.manufacturers =
state.manufacturers.filter(manufacturer => manufacturer._id !== manufacturerId);
}, [UPDATE_MANUFACTURER](state) { state.showLoader = true; },
[UPDATE_MANUFACTURER_SUCCESS](state, payload) { state.showLoader = false; const
{ manufacturer: newManufacturer } = payload; state.manufacturers =
state.manufacturers.map(manufacturer => { if (manufacturer._id ===
newManufacturer._id) { return newManufacturer; } return manufacturer; }) },
[ADD_MANUFACTURER](state) { state.showLoader = true; },
[ADD_MANUFACTURER_SUCCESS](state, payload) { state.showLoader = false; const {
manufacturer } = payload; state.manufacturers =
state.manufacturers.concat(manufacturer); } }

如此就完成了用常數替換 actionsmutations 兩隻檔案中的事件類型
後續要維護就不會東一塊西一塊的過於分散,還要用全域搜尋人工替換,增加了維護的可行性


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

最新文章

文章分類

Tag