一隻箱子裡的貓,看著電腦螢幕

Schrödinger's Programmer

奔跑吧工程師,趁年輕跑得越遠越好

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

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

Ray

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

建立 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

最新文章

Category

Tag