從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(六)

本文由圖雀社區成員 Holy 使用 Tuture 實戰教程寫做工具 寫做而成,歡迎加入圖雀社區,一塊兒創做精彩的免費技術實戰教程,予力編程行業發展。javascript

前面五篇教程咱們已經基本實現了迷你全棧電商應用的界面展現以及功能邏輯,相信你們在這個過程當中都收穫頗豐,而且邁向了全棧工程師的第一步。可是咱們卻並不知足於此,咱們還須要對咱們的項目代碼進行優化,使得咱們的代碼可讀性更高,也更好維護。相信細心的大家已經感受到了項目中的store實例實在是過於臃腫,所以,本篇教程就是帶你們一塊兒學習如何抽出 Getters 、 Mutations 和Actions 邏輯實現store的「減重」以及如何幹掉 mutation-types 硬編碼。css

歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:html

若是你但願直接從這一步開始,請運行如下命令:vue

git clone -b section-six https://github.com/tuture-dev/vue-online-shop-frontend.git
cd vue-online-shop-frontend
複製代碼

本文所涉及的源代碼都放在了 Github 上,若是您以爲咱們寫得還不錯,但願您能給❤️這篇文章點贊+Github倉庫加星❤️哦~java

抽出 Getters 和 Mutations 邏輯

這一節咱們來學習如何抽出在store實例中定義的複雜gettersmutation邏輯。ios

咱們發現以前咱們直接把全部的getter屬性和方法都定義在了store實例中的getters屬性中,全部的mutation屬性也都定義在了store實例中的mutations屬性中,這樣顯得store實例特別的累贅,所以咱們能夠經過對象展開運算符將這些複雜的邏輯抽取到對應的 GettersMutations文件中。git

重構 Admin 入口文件

首先咱們作一點本土化,把以前的 src/pages/admin/Index.vue 中的英文導航改爲中文版,方便查看;而且咱們增長了查看生產商導航。github

<template>
  <div>
    <div class="admin-new">
      <div class="container">
        <div class="col-lg-3 col-md-3 col-sm-12 col-xs-12">
          <ul class="admin-menu">
            <li>
              <router-link to="/admin">查看商品</router-link>
            </li>
            <li>
              <router-link to="/admin/new">添加商品</router-link>
            </li>
            <li>
              <router-link to="/admin/manufacturers">查看生產商</router-link>
            </li>
          </ul>
        </div>
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>
複製代碼

這裏咱們將有關商品的導航欄修改成中文版,讓用戶可以秒懂;除此以外咱們又添加了有關製造商的導航,這裏增長的是查看生產商導航,並添加了對應的導航跳轉路徑,該路徑須要與對應路由參數一致。vuex

建立 Manufacturers 組件

咱們建立的src/pages/admin/Manufacturers.vue文件是本地製造商組件,用於展現製造商的信息。編程

<template>
  <div>
    <table class="table">
      <thead>
        <tr>
          <th>製造商</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="manufacturer in manufacturers" :key="manufacturer._id">
          <td>{{manufacturer.name}}</td>
          <td class="modify"><router-link :to="'/admin/manufacturers/edit/' + manufacturer._id">修改</router-link></td>
          <td class="remove"><a @click="removeManufacturer(manufacturer._id)" href="#">刪除</a></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<style> table { margin: 0 auto; } .modify { color: blue; } .remove a { color: red; } </style>

<script> export default { created() { if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers } }, methods: { removeManufacturer(manufacturerId) { // 使用 JavaScript BOM 的 confirm 方法來詢問用戶是否刪除此製造商 const res = confirm('是否刪除此製造商?'); // 若是用戶贊成,那麼就刪除此製造商 if (res) { this.$store.dispatch('removeManufacturer', { manufacturerId, }) } } } } </script>
複製代碼

這裏首先定義了一個計算屬性manufacturers,經過this.$store.getters.allManufacturers屬性訪問的形式調用對應的getter屬性allManufacturers從本地獲取manufacturers,並返回給計算屬性manufacturers

而後在該組件剛被建立時判斷本地中是否存在manufacturers,若是沒有則經過this.$store.dispatch分發到類型爲allManufacturersaction中進行異步操做獲取全部製造商,並將獲取的製造商提交到對應的mutation中,在mutation中修改本地狀態,將獲取的全部製造商保存到本地。

最後利用v-for在表格中遍歷manufacturers,每一個製造商的信息在一行展現,除了信息以外還有兩個功能(修改和刪除製造商),點擊修改則會根據'/admin/manufacturers/edit/' + manufacturer._id路由到指定頁面;點擊刪除則會觸發removeManufacturer事件,首先詢問用戶是否贊成刪除,若用戶贊成則將選中製造商的id做爲載荷分發到類型爲removeManufactureraction中,在action中進行異步操做刪除後端對應商品,並將對應商品id提交到對應的mutation中,在mutation中進行本地狀態修改,刪除本地對應的商品。

重構 Products 組件

根據Manufacturers組件的設計原則,咱們須要再次進入src/pages/admin/Products.vue文件。按照Manufacturers組件的UI展現以及數據處理,將Products組件進行一下重構。

<template>
  <div>
    <table class="table">
      <thead>
        <tr>
          <th>名稱</th>
          <th>價格</th>
          <th>製造商</th>
          <th></th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="product in products" :key="product._id">
          <td>{{product.name}}</td>
          <td>{{product.price}}</td>
          <td>{{product.manufacturer.name}}</td>
          <td class="modify"><router-link :to="'/admin/edit/' + product._id">修改</router-link></td>
          <td class="remove"><a @click="removeProduct(product._id)" href="#">刪除</a></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<style> table { margin: 0 auto; } .modify { color: blue; } .remove a { color: red; } </style>

<script> export default { created() { if (this.products.length === 0) { this.$store.dispatch('allProducts'); } }, computed: { products() { return this.$store.getters.allProducts } }, methods: { removeProduct(productId) { // 使用 JavaScript BOM 的 confirm 方法來詢問用戶是否刪除此商品 const res = confirm('是否刪除此商品?'); // 若是用戶贊成,那麼就刪除此商品 if (res) { this.$store.dispatch('removeProduct', { productId, }) } } } } // ... 複製代碼

這部分代碼邏輯與src/pages/admin/Manufacturers.vue文件中的代碼邏輯類似,若是您理解了上面的代碼邏輯,那麼咱們相信您對這裏的代碼也能融會貫通,因此這裏就再也不贅述了。

添加路由信息

咱們已經建立了有關製造商的導航以及查看製造商組件,還需對其配置相應的路由參數才能實現跳轉。所以再次進入src/router/index.js文件,這裏咱們導入了製造商組件並增長了製造商相關路由參數。

// ...
import New from '@/pages/admin/New';
import Products from '@/pages/admin/Products';
import Edit from '@/pages/admin/Edit';
import Manufacturers from '@/pages/admin/Manufacturers';

Vue.use(Router);

// ...
          name: 'Edit',
          component: Edit,
        },
        {
          path: 'manufacturers',
          name: 'Manufacturers',
          component: Manufacturers,
        },
      ]
    },
    {
      // ...
複製代碼

把項目跑起來,點擊Admin而後再點擊查看生產商,咱們能夠看到從後端獲取的全部生產商:

下面展現的就是從後端獲取的製造商,而且咱們能夠對其進行修改和刪除操做。

抽取 Getters 邏輯

咱們首先建立了src/store/getters.js文件,用於存放各類不一樣類型的getter屬性和方法。這裏咱們導出了兩個對象分別爲productGettersmanufacturerGetters,前者包含了有關商品的getter屬性與方法,後者包含了有關製造商的getter屬性與方法。

export const productGetters = {
  allProducts(state) {
    return state.products
  },
  productById: (state, getters) => id => {
    if (getters.allProducts.length > 0) {
      return getters.allProducts.filter(product => product._id === id)[0]
    } else {
      return state.product;
    }
  }
}

export const manufacturerGetters = {
  allManufacturers(state) {
    return state.manufacturers;
  }
}
複製代碼

productGetters對象中定義的就是有關商品的getter屬性和方法,如allProductsproductById等等;在manufacturerGetters對象中定義的就是有關製造商的getter屬性和方法,如allManufacturers等等。

咱們能夠採用屬性調用和方法調用的方式調用這裏的getter(根據咱們在getter對象中定義的是屬性仍是方法)

抽取 Mutations 邏輯

一樣的咱們建立了src/store/mutations.js文件,用於存放從store實例的mutations屬性中抽取出來的各類mutation屬性,這裏咱們定義了三個對象分別爲productMutationscartMutations以及manufacturerMutations

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);
  }
};

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;
  },
  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);
  }
}
複製代碼

productMutations對象中定義了有關商品響應Vue視圖層以及avtion中提交的事件,好比ALL_PRODUCTSALL_PRODUCTS_SUCCESSPRODUCT_BY_ID以及PRODUCT_BY_ID_SUCCESS等等。

cartMutations對象中定義了有關購物車響應Vue視圖層提交的事件,好比ADD_TO_CARTREMOVE_FROM_CART等等。

manufacturerMutations對象中定義了有關製造商響應Vue視圖層以及avtion中提交的事件,好比ALL_MANUFACTURERSALL_MANUFACTURERS_SUCCESSREMOVE_MANUFACTURER以及REMOVE_MANUFACTURER_SUCCESS等等。

重構 Store 實例

咱們將store實例中的Getter屬性和Mutation屬性抽出以後要再進行導入。再回到src/store/index.js文件,這裏就是抽離gettersmutations邏輯以後的store實例,看起來是否是輕盈了不少,也加強了代碼的可讀性。

// ...
import Vuex from 'vuex';
import axios from 'axios';

import { productGetters, manufacturerGetters } from './getters';
import { productMutations, cartMutations, manufacturerMutations } from './mutations';

const API_BASE = 'http://localhost:3000/api/v1';

Vue.use(Vuex);
 // ...
    manufacturers: [],
  },
  mutations: {
    ...productMutations,
    ...cartMutations,
    ...manufacturerMutations,
  },
  getters: {
    ...productGetters,
    ...manufacturerGetters,
  },
  actions: {
    allProducts({ commit }) {
      // ...
          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,
        });
      })
    },
    allManufacturers({ commit }) {
      commit('ALL_MANUFACTURERS');

      axios.get(`${API_BASE}/manufacturers`).then(response => {
        commit('ALL_MANUFACTURERS_SUCCESS', {
          manufacturers: response.data,
        });
      })
    },
    removeManufacturer({ commit }, payload) {
      commit('REMOVE_MANUFACTURER');

      const { manufacturerId } = payload;
      axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => {
        // 返回 manufacturerId,用於刪除本地對應的製造商
        commit('REMOVE_MANUFACTURER_SUCCESS', {
          manufacturerId,
        });
      })
    },
  }
});
複製代碼

這裏首先導入了gettersmutations文件中導出的全部對象,而後在store實例的gettersmutations屬性中經過對象展開運算符的方式將對應的屬性和方法導入到store實例中。

對象展開運算符是ES7草案中的新特性,將一個對象當中的對象的一部分取出來成爲一個新對象賦值給展開運算符的參數,而後插入到另一個對象當中。例如:

let shortcuts = {
    attr1: 3,
    attr2: 4
}
let shortcuts2 = {}
shortcuts2 = {...shortcuts}

//上面的這種用法實際上至關因而:
shortcuts2 = {attr1: 3, attr2: 4}
複製代碼

除此以外咱們又在actions中添加了一些其餘的action屬性,由於此時actions還未被抽離,因此可能依然顯得有些臃腫,不過在後面咱們立刻也會將它抽離出來。

小結

這一節咱們學習瞭如何抽出GettersMutations邏輯,減輕store實例中的負載:

  • 首先咱們須要分別建立gettersmutationsJS文件,在兩個JS文件中分別定義不一樣類型的gettersmutations對象並導出,而後在gettersmutations對象中定義相應的一些屬性和方法。
  • storeindex文件中導入這些gettersmutations對象,並在store實例的gettersmutations屬性中經過對象展開運算符混入這些對象。
  • 咱們可使用this.$store.getters.屬性this.$store.mutations.屬性的方式調用.

抽出 Actions 邏輯

上一節咱們學習瞭如何抽出GettersMutations邏輯,這一節咱們以一樣的方式抽出Actions邏輯。

重構 Edit 組件

src/pages/admin/Edit.vue是商品編輯組件,當觸發'/admin/edit/' + product._id就會路由到指定商品信息編輯頁面,而後對商品信息進行修改。

以前咱們直接將展現商品信息的代碼放在該組件中,可是咱們發現展現商品信息這部分功能在新建商品和編輯商品組件中都須要使用,所以咱們打算把這部分代碼封裝爲一個展現商品信息的表單組件ProductForm,這樣的話咱們在新建商品和編輯商品組件中都能複用該組件。

除此以外咱們還在該組件中添加了數據處理功能。

<template>
  <div>
    <div class="title">
      <h1>This is Admin/Edit</h1>
    </div>
    <product-form @save-product="updateProduct" :model="model" :manufacturers="manufacturers" :isEditing="true" ></product-form>
  </div>
</template>

<script> import ProductForm from '@/components/products/ProductForm.vue'; export default { created() { const { name } = this.model; if (!name) { this.$store.dispatch('productById', { productId: this.$route.params['id'] }); } if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers; }, model() { const product = this.$store.getters.productById(this.$route.params['id']); // 這裏返回 product 的拷貝,是爲了在修改 product 的拷貝以後,在保存以前不修改本地 Vuex stire 的 product 屬性 return { ...product, manufacturer: { ...product.manufacturer } }; } }, methods: { updateProduct(product) { this.$store.dispatch('updateProduct', { product, }) } }, components: { 'product-form': ProductForm } } </script>
複製代碼

咱們先來看該組件的script部分,首先定義了兩個計算屬性modelmanufacturers返回本地商品和製造商。經過方法訪問的方式調用指定的getter屬性productById,參數爲當前處於激活狀態的路由對象的id,這裏返回product的拷貝,是爲了在修改 product的拷貝以後,在保存以前不修改本地 Vuex store 的product屬性。計算屬性manufacturers經過相同的方式獲取本地數據。

當該組件剛被建立時判斷計算屬性model中是否有值,若是沒有則表示本地中沒有該商品,將包含該商品id的對象做爲載荷分發到類型爲productByIdaction中,在action中進行異步操做從後端獲取對應商品,並提交到對應類型的mutation中,在mutation中將獲取到的商品保存到本地。除此以外判斷計算屬性manufacturers中是否有值,若是沒有則經過相同的方式從後端獲取並保存到本地。

template中使用了子組件ProductForm用表單的形式來展現商品信息,當用戶提交表單則會向父組件發射save-product事件,父組件監聽到以後觸發updateProduct事件,並將傳入的商品參數做爲載荷分發到類型爲updateProductaction中,通知後端進行同步更新數據並提交到對應的mutation中進行本地數據更新。

重構 New 組件

src/pages/admin/New.vue是添加商品組件,與Edit組件的代碼邏輯類似,只是一個是修改商品信息,一個是添加商品信息。

咱們將該組件中原先寫死的數據改爲了從後端動態獲取, 並將獲取的數據傳遞給子組件ProductForm。

<template>
  <product-form @save-product="addProduct" :model="model" :manufacturers="manufacturers" >
  </product-form>
</template>

<script> import ProductForm from '@/components/products/ProductForm.vue'; export default { created() { if (this.manufacturers.length === 0) { this.$store.dispatch('allManufacturers'); } }, computed: { manufacturers() { return this.$store.getters.allManufacturers; }, model() { return {}; } }, methods: { addProduct(model) { this.$store.dispatch('addProduct', { product: model, }) }, }, components: { 'product-form': ProductForm } } </script>
複製代碼

該組件代碼邏輯和Edit.vue組件類似,只是在這裏咱們定義的計算屬性model返回一個空對象做爲默認值,由於咱們是添加商品,本地中還不存在該商品。

抽取 Actions 邏輯

像以前同樣咱們建立了src/store/actions.js文件,用於存儲從store實例的actions屬性中抽取出來的不一樣類型的action屬性。這裏咱們定義了兩個Actions對象:productActionsmanufacturerActions,分別表示有關商品和製造商對視圖層分發的事件做出的響應,並導出了這兩個對象。

import axios from 'axios';

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,
      });
    })
  },
  removeManufacturer({ commit }, payload) {
    commit('REMOVE_MANUFACTURER');

    const { manufacturerId } = payload;
    axios.delete(`${API_BASE}/manufacturers/${manufacturerId}`).then(() => {
      // 返回 manufacturerId,用於刪除本地對應的製造商
      commit('REMOVE_MANUFACTURER_SUCCESS', {
        manufacturerId,
      });
    })
  },
}
複製代碼

在該文件中咱們首先導入axios依賴,以及定義了 API_BASE 後端接口根路由;

而後咱們定義並導出了兩個對象:

  • productActions對象中定義了一些有關商品在視圖層分發對應的事件時,action做出的響應,好比allProductsproductByIdremoveProduct以及updateProduct等等。
  • manufacturerActions對象中定義了一些有關製造商在視圖層分發對應的事件時,action做出的響應,好比allManufacturersremoveManufacturer等等。

重構 Store 實例

咱們再次來到src/store/index.js文件中,添加有關抽取Actions邏輯以後的信息。

import Vue from 'vue';
import Vuex from 'vuex';

import { productGetters, manufacturerGetters } from './getters';
import { productMutations, cartMutations, manufacturerMutations } from './mutations';
import { productActions, manufacturerActions } from './actions';

Vue.use(Vuex);

// ...
  actions: {
    ...productActions,
    ...manufacturerActions,
  }
});
複製代碼

這裏咱們首先導入了actions.js文件中導出的一些Action對象,並經過對象展開運算符在store實例的actions屬性中混入了不一樣類型的action屬性,實現了Actions邏輯的抽取。

添加 mutations 屬性

咱們在src/store/mutations.js文件中又添加了一些mutation屬性,用於用戶進行不一樣的操做進行本地數據的同步。

// ...

    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);
  }
};

// ...
複製代碼

上述添加的都是有關商品的mutation屬性:UPDATE_PRODUCTUPDATE_PRODUCT_SUCCESSADD_PRODUCT以及ADD_PRODUCT_SUCCESS分別表示更新商品信息,更新商品信息成功,添加商品以及添加商品成功。

小結

這一節咱們學習瞭如何抽出Actions邏輯,減輕store實例中的負載:

  • 首先咱們須要建立actionsJS文件,在文件中定義不一樣類型的Actions對象並導出,而後在Actions對象中定義相應的一些屬性。
  • storeindex文件中導入這些Actions對象,並在store實例的actions屬性中經過對象展開運算符混入這些對象。
  • 咱們可使用this.$store.actions.屬性的方式調用。

幹掉 mutation-types 硬編碼

這一節咱們主要是進一步完善咱們的項目功能以及去掉一些硬編碼。

建立 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>
複製代碼

該組件經過父子組件傳值從父組件獲取到了modelisEditing的值,並將model對象的信息展現在表單中。

表單信息中還經過v-if來判斷isEditing的值是true仍是false,若是是true則建立Update Manufacturer,反之建立Add Manufacturer

當用戶提交表單時觸發saveManufacturer事件,此時會向父組件發送類型爲save-manufacturer的事件通知其保存這次的修改操做。

重構 getters 文件

在建立編輯製造商組件以前,咱們須要在getters文件中添加對應的getter屬性。

咱們在src/store/getters.js文件的manufacturerGetters對象中又添加了一個manufacturerById方法,用於獲取本地中指定的製造商。

// ...
export const manufacturerGetters = {
  allManufacturers(state) {
    return state.manufacturers;
  },
  manufacturerById: (state, getters) => id => {
    if (getters.allManufacturers.length > 0) {
      return getters.allManufacturers.filter(manufacturer => manufacturer._id === id)[0]
    } else {
      return state.manufacturer;
    }
  }
}
複製代碼

manufacturerById方法中的id參數是Vue視圖層經過方法調用時傳入的id,經過這個id判斷本地中是否存在該製造商,若是存在則返回該製造商,若是不存在則返回一個空對象。

建立 EditManufacturers 組件

在建立了展現製造商信息的表單組件ManufacturerForm以及添加了用於獲取本地指定製造商數據的getter屬性以後,緊接着咱們又建立了src/pages/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']); // 這裏返回 product 的拷貝,是爲了在修改 product 的拷貝以後,在保存以前不修改本地 Vuex stire 的 product 屬性 return { ...manufacturer }; } }, methods: { addManufacturer(model) { this.$store.dispatch('updateManufacturer', { manufacturer: model, }) }, }, components: { 'manufacturer-form': ManufacturerForm } } </script>
複製代碼

該組件剛被建立時將當前處於激活狀態的路由對象的id參數做爲載荷分發到類型爲manufacturerByIdaction中,在action中進行異步操做從服務器獲取對應制造商,而後將該製造商提交到對應mutation中進行本地狀態修改,將獲取到的製造商保存到本地。

咱們定義了計算屬性model返回manufacturer的拷貝,是爲了在修改manufacturer的拷貝以後,在保存以前不修改本地 store中的manufacturer屬性。這裏以方法訪問的形式從getters中經過當前激活的路由對象中的id參數獲取本地狀態中的對應制造商做爲manufacturer的拷貝,並返回給計算屬性model,而後傳給子組件ManufacturerForm

該組件在addManufacturer事件中將子組件傳入的新制造商對象做爲載荷分發到類型爲updateManufactureraction中,在action中進行異步操做修改後端對應的商品信息,而後將新對象提交到對應的mutation中進行本地狀態修改,修改本地狀態中的manufacturer對象。

建立 NewManufacturers 組件

一樣的咱們繼續建立了src/pages/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>
複製代碼

該組件邏輯代碼與New.vue組件相似,一個是添加商品組件,一個是添加製造商組件,您能夠對比着來看。

重構 Admin 入口文件

以前咱們在該入口文件中增長了查看生產商導航,這裏咱們又增長了添加生產商導航。

// ...
            <li>
              <router-link to="/admin/manufacturers">查看生產商</router-link>
            </li>
            <li>
              <router-link to="/admin/manufacturers/new">添加生產商</router-link>
            </li>
          </ul>
        </div>
        <router-view></router-view>
      // ...
複製代碼

添加路由信息

咱們已經建立了添加和修改製造商組件以及添加了對應的入口導航,接着咱們須要在該文件中對其進行路由參數配置。

再次進入src/router/index.js文件,咱們導入了添加製造商和修改製造商的組件並配置了相關路由參數。

// ...
import Products from '@/pages/admin/Products';
import Edit from '@/pages/admin/Edit';
import Manufacturers from '@/pages/admin/Manufacturers';
import NewManufacturers from '@/pages/admin/NewManufacturers';
import EditManufacturers from '@/pages/admin/EditManufacturers';

Vue.use(Router);

// ...
          name: 'Manufacturers',
          component: Manufacturers,
        },
        {
          path: 'manufacturers/new',
          name: 'NewManufacturers',
          component: NewManufacturers,
        },
        {
          path: 'manufacturers/edit/:id',
          name: 'EditManufacturers',
          component: EditManufacturers,
        },
      ]
    },
    {
      // ...
複製代碼

這裏添加製造商的路由配置就是靜態路由的配置方式;修改製造商的路由配置採用了動態傳參的方式,這裏使用的是$route.params對象的Post方式傳參。

配置好添加製造商和修改製造商的路由參數以後,咱們又能夠進行驗收啦,運行項目,點擊Admin而後再點擊添加製造商,咱們能夠看到添加製造商的表單:

而後再點擊查看製造商,咱們又能夠看到一系列的製造商,而且每一個製造商都對應的有修改和刪除操做,這裏咱們隨便選擇一個製造商進行修改,就會看到該製造商的表單信息:

建立 mutation-types 文件

這一節咱們將對咱們的項目代碼進行優化,幹掉一些硬編碼。

咱們都知道在actions文件和mutations文件中有一部分的事件類型是須要保持一致的,好比在咱們在視圖層分發一個添加商品事件ADD_PRODUCT,在actions文件中就須要有對應事件類型的action接收,而後向後端發起請求並將請求結果提交到對應類型的mutation中,這就要求了這幾個文件中的對應事件類型都要保持一致。但是咱們在開發過程當中不免會出錯,好比漏掉一個字母就會致使兩個文件中的對應事件沒法接收,尷尬的是控制檯也沒有報錯,這就形成了咱們很難查錯。

所以,咱們採用了字符串常量的形式定義actions文件和mutations文件中的事件類型,只要咱們寫錯一個單詞都會致使字符串常量不一致,關鍵的是這個時候會報錯,利於咱們查錯。

進而咱們建立了src/store/mutation-types.js文件,用於定義一些字符串常量來表示各類事件類型,並導出這些字符串常量。

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,
      })
    })
  }
}
複製代碼

這裏咱們首先導入了mutation-types文件中定義的一些字符串常量,替換掉了對應的事件類型。

重構 mutations 文件

同actions文件同樣,咱們再次進入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;
    // ...
      return product;
    })
  },
  [ADD_PRODUCT](state) {
    state.showLoader = true;
  },
  [ADD_PRODUCT_SUCCESS](state, payload) {
    state.showLoader = false;

    const { product } = payload;
    // ...
};

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);
  }
}
複製代碼

這裏咱們首先導入了mutation-types文件中定義的一些字符串常量,替換掉了對應的事件類型。

小結

這一節咱們主要作了如下工做:

  • 建立了製造商相關組件並配置了相應路由參數,實現了新建和修改製造商信息;
  • 利用字符串常量代替actions文件和mutations文件中的事件類型;

想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。

本文所涉及的源代碼都放在了 Github 上,若是您以爲咱們寫得還不錯,但願您能給❤️這篇文章點贊+Github倉庫加星❤️哦

相關文章
相關標籤/搜索