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

tuture發表於2020-05-10

隨著前端應用的日漸複雜,狀態和資料管理成為了構建大型應用的關鍵。受 Redux 等專案的啟發,Vue.js 團隊也量身定做了狀態管理庫 Vuex。在這篇教程中,我們將帶你熟悉 Store、Mutation 和 Action 三大關鍵概念,並升級迷你商城應用的前端程式碼。

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

如果您覺得我們寫得還不錯,記得 點贊 + 關注 + 評論 三連,鼓勵我們寫出更好的教程?

使用 Vuex 進行狀態管理

我們在第一篇第三篇中講解了 Vue 的基礎部分,利用這些知識你已經可以實現一些比較簡單的應用了。但是針對複雜的應用,比如元件巢狀超過三級,我們前面講解的知識處理起來就很費力了,還好 Vue 社群為我們打造了狀態管理容器 Vuex,用來處理大型應用的資料和狀態管理。

安裝 Vuex 依賴

首先我們開啟命令列,進入專案目錄,執行如下命令安裝 Vuex:

npm install vuex
複製程式碼

建立 Vuex Store

Vuex 是一個前端狀態管理工具,它致力於接管 Vue 的狀態,使得 Vue 專心做好渲染頁面的事情;它類似在前端建立了一個 “資料庫”,然後將所有的前端狀態都儲存在這個 “資料庫” 裡面。這個 “資料庫” 其實就是一個普通的 JavaScript 物件。

好了,講述了 Vuex 是幹什麼之後,我們來看一下如何在 Vue 中運用 Vuex。Vuex 建立的這個 “資料庫” 一般用術語 store 來表示,通常我們建立一個單獨的 store 資料夾,用於儲存和 store 有關的內容。我們在 src 資料夾下建立 store 資料夾,然後在裡面建立 index.js 檔案,程式碼如下:

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

Vue.use(Vuex);

export default new Vuex.Store({
  strict: true,
  state: {
    // bought items
    cart: [],
    // ajax loader
    showLoader: false,
    // selected product
    product: {},
    // all products
    products: [
      {
        name: '1',
      }
    ],
    // all manufacturers
    manufacturers: [],
  }
})
複製程式碼

上面的程式碼可以分為三部分。

  • 首先我們匯入 VueVuex
  • 然後我們呼叫 Vue.use 方法,告訴 Vue 我們將使用 Vuex,這和我們之前使用 Vue.use(router) 的原理一樣
  • 最後我們匯出 Vuex.Store 例項,並且傳入了 strictstate 引數。這裡 strict 參數列示,我們必須使用 Vuex 的 Mutation 函式來改變 state,否則就會報錯(關於 Mutation 我們將在 “使用 Vuex 進行狀態管理” 一節講解)。而 state 引數用來存放我們全域性的狀態,比如我們這裡定義了 cartshowLoader 等屬性都是後面我們完善應用的內容需要的資料。

將 Vuex 和 Vue 整合

當我們建立並匯出了 Vuex 的 store 例項之後,我們就可以使用它了。開啟 src/main.js 檔案,在開頭匯入之前建立的 store ,並將 store 新增到 Vue 初始化的引數列表裡,程式碼如下:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import { ValidationProvider } from 'vee-validate';

import App from './App';
import router from './router';
import store from './store';

Vue.config.productionTip = false;
Vue.component('ValidationProvider', ValidationProvider);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>',
});
複製程式碼

可以看到,在上面的檔案中,我們一開頭匯入了我們之前在 src/store/index.js 裡定義的 store 例項,接著,在 Vue 例項初始化時,我們將這個 store 例項使用物件屬性簡潔寫法新增到了引數列表裡。

當我們將 store 當做引數傳給 Vue 進行初始化之後,Vue 就會將 Store 裡面的 state 注入到所有的 Vue 元件中,這樣所有的 Vue 元件共享同一個全域性的 state ,它其實就是一個 JS 物件,應用中所有狀態的變化都是對 state 進行操作,然後響應式的觸發元件的重新渲染,所以這裡的 state 也有 “資料的唯一真相來源” 的稱謂。

這種將狀態儲存到一個全域性的 JavaScript 物件 -- state 中,然後所有的增、刪、改、查操作都是對這個 JavaScript 物件進行,使得我們可以避免元件巢狀層級過深時,元件之間傳遞屬性的複雜性,讓屬性的定義,獲取,修改非常直觀,方便開發大型應用和團隊協作。

檢視 Vuex 整合後的效果

在將 Vuex 和 Vue 整合好之後,我們馬上來看一下 Vuex 帶來的效果,不過在此之前我們先來講一講什麼是計算屬性(computed)。

計算屬性(Computed)

首先我們新增了 script 部分,然後在匯出的物件裡面增加了一個 computed 屬性,這個屬性裡面的內容用於申明一些可能要在 template 裡面使用的複雜表示式。我們來看一個例子來講解一下 computed 屬性:

我們在模板中可能要獲取一個多級巢狀物件裡面的某個資料,或者要渲染的資料需要經過複雜的表示式來計算,比如我們要渲染這樣一個資料 obj1.obj2.obj3.a + obj1.obj4.b,寫在模板裡就是這樣的:

<template>
<div>
  {{ obj1.obj2.obj3.a + obj1.obj4.b }}
</div>
</template>
<script>
export default {
  data: {
    obj1: {
      obj2: {
        obj3: {
          a
        }
      },
      obj4: {
        b
      }
    }
  }
}
</script>
複製程式碼

可以看到,我們一眼看上去,這個模板裡面有這樣一個複雜的表示式,很不容易反應出來它到底要渲染什麼,這樣程式碼的可讀性就很差,所以 Vue 為我們提供了計算屬性( computed ),用於用簡單的變數來代表複雜的表示式結果,進而簡化模板中插值的內容,讓我們的模板看起來可讀性更好,上面的程式碼使用計算屬性來改進會變成下面這樣:

<template>
<div>
  {{ addResult }}
</div>
</template>

<script>
export default {
  data: {
    obj1: {
      obj2: {
        obj3: {
          a
        }
      },
      obj4: {
        b
      }
    }
  },
  computed: {
    addResult() {
      return this.obj1.obj2.obj3.a + this.obj1.obj4.b
    }
  }
}
</script>
複製程式碼

可以看到,當我們使用了計算屬性 addResult 之後,我們在模板裡面的寫法就簡化了很多,而且一目瞭然我們是渲染了什麼。

瞭解了計算屬性之後,我們開啟 src/pages/admin/Products.vue,對內容作出如下改進以檢視 Vuex 和 Vue 整合之後的效果:

<template>
  <div>
    <div class="title">
      <h1>This is Admin</h1>
    </div>
    <div class="body">
      {{ product.name }}
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    product() {
      return this.$store.state.products[0];
    }
  }
}
</script>
複製程式碼

可以看到,上面的內容改進主要分為兩個部分:

  • 首先我們定義了一個 product 計算屬性,它裡面返回一個從 store 中儲存的 state 取到的 products 陣列的第一個元素,注意到當我們在 “將 Vuex 和 Vue 整合” 這一小節中將 store 作為 Vue 初始化例項引數,所以我們在所有的 Vue 元件中可以通過 this.$store.state 的形式取到 Vuex Store 中儲存的 state
  • 接著我們使用了計算屬性 product,取到了它的 name 屬性進行渲染。

小結

在這一小節中,我們學習瞭如何將 Vuex 整合進 Vue 中:

  • 首先我們安裝了 vuex 依賴
  • 接著我們在 src 下面建立了 store 資料夾,用於儲存 Vuex 相關的內容,在 store 檔案下之下,我們建立了 index.js 檔案,在裡面例項化了 Vuex.Store 類,我們在例項化的過程中傳遞了兩個引數:strictstatestrict 表示我們告訴 Vue,只允許 Mutation 方法才能修改 state,確保修改狀態的唯一性;state 是我們整個應用的狀態,整個應用的狀態都是從它獲取,整個應用狀態的改變都是修改它,所以這個 state 也有 “資料的唯一真相來源” 的稱謂。
  • 然後我們通過在 main.js 裡面匯入例項化的 store,將它加入到初始化 Vue 例項的引數列表中,實現了 Vuex 和 Vue 的整合。
  • 最後我們講解了計算屬性,然後通過在計算屬性中獲取 this.$store.state 的方式展示了 Vuex 整合之後的效果。

好了,我們已經整合了 Vuex,並在 Vue 元件中獲取了儲存在 Vuex Store 中的狀態(state),接下來我們來看一下如何修改這個狀態。

使用 Mutation 修改本地狀態

我們在上一節中定義了 Vuex Store,並在裡面儲存了全域性的狀態 state,這一節我們來學習如何修改這一狀態。

理解 Mutation:修改狀態的唯一手段

Vuex 為我們提供了 Mutation,它是修改 Vuex Store 中儲存狀態的唯一手段。

Mutation 是定義在 Vuex Store 的 mutation 屬性中的一系列形如 (state, payload) => newState 的函式,用於響應從 Vue 檢視層發出來的事件或動作,例如:

ACTION_NAME(state, payload) {
  // 對 `state` 進行操作以返回新的 `state`
  return newState;
}
複製程式碼

其中方法名 ACTION_NAME 用於對應從檢視層裡面發出的事件或動作的名稱,這個函式接收兩個引數 statepayloadstate 就是我們 Vuex Store 中儲存的 statepayload 是被響應的那個事件或動作攜帶的引數,然後我們通過 payload 的引數來操作現有的 state,返回新的 state,通過這樣的方式,我們就可以響應修改 Vuex Store 中儲存的全域性狀態。

瞭解了 Mutation 的概念之後,我們馬上來看一下如何運用它。

初始化狀態(硬編碼)

我們開啟 src/store/index.js 檔案,修改其中的 state 並加上 mutations 如下:

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

Vue.use(Vuex);

export default new Vuex.Store({
  strict: true,
  state: {
    // bought items
    cart: [],
    // ajax loader
    showLoader: false,
    // selected product
    product: {},
    // all products
    products: [
      {
        _id: '1',
        name: 'iPhone',
        description: 'iPhone是美國蘋果公司研發的智慧手機系列,搭載蘋果公司研發的iOS作業系統',
        image: 'https://i.gadgets360cdn.com/large/iPhone11_leak_1567592422045.jpg',
        price: 2000,
        manufacturer: 'Apple Inc'
      },
      {
        _id: '2',
        name: '榮耀20',
        description: '李現同款 4800萬超廣角AI四攝 3200W美顏自拍 麒麟Kirin980全網通版8GB+128GB 藍水翡翠 全面屏手機',
        image: 'https://article-fd.zol-img.com.cn/t_s640x2000/g4/M08/0E/0E/ChMlzF2myueILMN_AAGSPzoz23wAAYJ3QADttsAAZJX090.jpg',
        price: 2499,
        manufacturer: '華為'
      },
      {
        _id: '3',
        name: 'MIX2S',
        description: '驍龍845 全面屏NFC 遊戲智慧拍照手機 白色 全網通 6+128',
        image: 'http://himg2.huanqiu.com/attachment2010/2018/0129/08/39/20180129083933823.jpg',
        price: 1688,
        manufacturer: '小米'
      },
      {
        _id: '4',
        name: 'IQOO Pro',
        description: '12GB+128GB 競速黑 高通驍龍855Plus手機 4800萬AI三攝 44W超快閃充 5G全網通手機',
        image: 'https://www.tabletowo.pl/wp-content/uploads/2019/08/vivo-iqoo-pro-5g-blue-1.jpg',
        price: 4098,
        manufacturer: 'Vivo'
      },
      {
        _id: '5',
        name: 'Reno2',
        description: '【12期免息1年碎屏險】4800萬變焦四攝8+128G防抖6.5英寸全面屏新 深海夜光(8GB+128GB) 官方標配',
        image: 'https://news.maxabout.com/wp-content/uploads/2019/08/OPPO-Reno-2-1.jpg',
        price: 2999,
        manufacturer: 'OPPO'
      }
    ],
    // 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)
    }
  }
});
複製程式碼

可以看到上面的程式碼改進分為兩個部分:

  • 首先我們擴充了 state 中的 products 屬性,在裡面儲存一開始我們的迷你電商平臺的初始資料,這裡我們是硬編碼到程式碼中的,在下一節 “使用 Action 獲取遠端資料”中,我們將動態獲取後端伺服器的資料。
  • 接著我們在 Vuex.Store 例項化的引數中新增了一個 mutations 屬性,在裡面定義了兩個函式 ADD_TO_CARTREMOVE_FROM_CART,分別代表響應從檢視層發起的對應將商品新增至購物車和從購物車移除商品的動作。

建立 ProductList 元件

接著建立 src/components/products/ProductList.vue 檔案,它是商品列表元件,用來展示商品的詳細資訊,程式碼如下:

<template>
  <div>
    <div class="products">
      <div class="container">
        This is ProductList
      </div>
      <template v-for="product in products">
        <div :key="product._id" class="product">
          <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}}</p>
          <img :src="product.image" alt="" class="product__image">
          <button @click="addToCart(product)">加入購物車</button>
        </div>
      </template>
    </div>
  </div>
</template>

<style>
.product {
  border-bottom: 1px solid black;
}

.product__image {
  width: 100px;
  height: 100px;
}
</style>

<script>
export default {
  name: 'product-list',
  computed: {
    // a computed getter
    products() {
      return this.$store.state.products;
    }
  },
  methods: {
    addToCart(product) {
      this.$store.commit('ADD_TO_CART', {
        product
      });
    }
  }
}
</script>
複製程式碼

我們首先來看該元件的 script 部分:

  • 先定義了一個計算屬性 products,通過 this.$store.state.products 從本地狀態中獲取到了 products 陣列,並作為計算屬性 products 的返回值
  • 然後定義了一個點選事件 addToCart,並且傳入了當前處於啟用狀態的 product 引數。當使用者點選“新增購物車”時,觸發 addToCart 事件,也就是上面所說的檢視層發出的事件。這裡是通過 this .$store.commit 將攜帶當前商品的物件 {product} 作為載荷提交到型別為ADD_TO_CARTmutation 中,在 mutation 中進行本地狀態修改,我們會在後面抽離出的 mutations 檔案中看到具體的操作。

再看該元件的 template 部分,使用 v-for 將從本地獲取到的 products 陣列進行遍歷,每個 product 物件的詳細資訊都會顯示在模板中。此外,我們還在每個 product 物件資訊的最後新增了一個“加入購物車”的按鈕,允許我們將指定商品新增到購物車。

在頁面中接入資料

Store 和元件都搞定之後,我們就可以在之前的頁面中接入資料了。修改主頁 src/pages/Home.vue,程式碼如下:

<template>
  <div>
    <div class="title">
      <h1>In Stock</h1>
    </div>
    <product-list></product-list>
  </div>
</template>

<script>
import ProductList from '@/components/products/ProductList.vue';
  export default {
    name: 'home',
    data () {
      return {
        msg: 'Welcome to Your Vue.js App'
      };
    },
    components: {
      'product-list': ProductList
    }
  }
</script>
複製程式碼

可以看到,我們在匯入 ProductList 元件後,將其註冊到 components 中,然後在模板中使用這個元件。

接著修改購物車頁面 src/pages/Cart.vue 檔案,將購物車中的商品資訊展示出來,新增程式碼如下:

<template>
  <div>
    <div class="title">
      <h1>{{msg}}</h1>
    </div>
    <template v-for="product in cart">
      <div :key="product._id" class="product">
        <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}}</p>
        <img :src="product.image" alt="" class="product__image">
        <button @click="removeFromCart(product._id)">從購物車中移除</button>
      </div>
    </template>
  </div>
</template>

<style>
.product {
  border-bottom: 1px solid black;
}

.product__image {
  width: 100px;
  height: 100px;
}
</style>

<script>
  export default {
    name: 'home',
    data () {
      return {
        msg: 'Welcome to the Cart Page'
      }
    },
    computed: {
      cart() {
        return this.$store.state.cart;
      }
    },
    methods: {
      removeFromCart(productId) {
        this.$store.commit('REMOVE_FROM_CART', {
          productId
        });
      }
    }
  }
</script>
複製程式碼

我們在該元件中主要增加了兩部分程式碼:

  • 首先是 script 部分,我們增加了一個計算屬性和一個點選事件。同樣是通過 this.$store.state.cart 的方式從本地狀態中獲取購物車陣列,並作為計算屬性 cart 的返回值;當使用者點選購物車中的某個商品將其移除購物車時就會觸發 removeFromCart 事件,並且將要移除的商品 id 作為引數傳入,然後也是通過 this.$store.commit 的方式將包含 productId 的物件作為載荷提交到型別為 REMOVE_FROM_CARTmutation 中,在 mutation 中進行本地狀態修改,具體修改操作我們可以在後面抽離出的 mutations 檔案中看到。

  • 然後是 template 部分,我們通過 v-for 遍歷了購物車陣列,將購物車中的所有商品資訊展示在模板中。並在每個商品資訊的最後新增了一個移除購物車的按鈕,當使用者希望移除購物車中指定商品時,會觸發 removeFromCart 事件。

檢視效果

在專案根目錄下執行 npm start,進入開發伺服器檢視效果:

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

可以看到,一開始我們的購物車是空的,然後隨便選了兩款手機,點選“加入購物車”,然後就可以在購物車頁面看到了!我們還可以將購物車中的商品移除。

小結

在這一部分中我們學習瞭如何發起修改本地狀態的“通知”:

  • 首先我們需要在 Vuex.Store 例項化的引數中新增一個 mutations 屬性,在該屬性中新增對應的方法,比如 ADD_TO_CARTREMOVE_FROM_CART
  • 然後我們需要通過使用者不同的操作(比如點選新增購物車或者移除購物車)來發起“通知”,進而通過 this.$store.commit 的方式將需要操作的物件作為載荷提交到對應型別(也就是 ADD_TO_CARTREMOVE_FROM_CART)的 mutation 中,在 mutation 中進行本地狀態修改。

使用 Action 獲取遠端資料

我們在上一節中學習瞭如何在檢視層發起本地狀態修改的“通知”,這一節我們來學習如何從後端獲取遠端資料。請求庫我們採用的是 axios,通過以下命令安裝依賴:

npm install axios
複製程式碼

理解 Action:非同步操作

Vuex 為我們提供了 Action,它是用來進行非同步操作,我們可以在這裡向後端發起網路資料請求,並將請求到的資料提交到對應的 mutation 中。

Action 是定義在 Vuex Store 的 action 屬性中的一系列方法,用於響應從 Vue 檢視層分發出來的事件或動作,一個 Action 是形如 (context, payload) => response.data 的函式:

productById(context, payload) {
  // 進行非同步操作,從後端獲取遠端資料並返回
  return response.data;
}
複製程式碼

其中:

  • 函式名 productById 用於對應從檢視層裡面分發出的事件或動作的名稱
  • 函式接收兩個引數 contextpayload
  • context 指的是 action 的上下文,與 store 例項具有相同的方法和屬性,因此我們可以呼叫 context.commit 提交一個 mutation,或者通過 context.statecontext.getters 來獲取 stategetters,但是 context 物件又不是 store 例項本身
  • payload 是分發時攜帶的引數,然後我們通過 payload 中的引數來進行非同步操作,從而獲取後端響應資料並返回。這樣我們就可以根據使用者的操作同步更新後端資料,並將後端響應的資料提交給 mutation,然後利用 mutation 進行本地資料更新。

實現第一個 Action

讓我們趁熱打鐵,實現第一個 Action。再次來到 src/store/index.js 檔案,修改程式碼如下:

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;
    }
  },
  actions: {
    allProducts({ commit }) {
      commit('ALL_PRODUCTS')

      axios.get(`${API_BASE}/products`).then(response => {
        console.log('response', response);
        commit('ALL_PRODUCTS_SUCCESS', {
          products: response.data,
        });
      })
    }
  }
});
複製程式碼

可以看到,我們做了以下幾件事:

  1. 匯入了 axios,並定義了 API_BASE 後端介面根路由;
  2. 我們在 store 中去掉了之前硬編碼的假資料,使 products 預設值為空陣列;
  3. 然後在 mutations 屬性中新增了 ALL_PRODUCTSALL_PRODUCTS_SUCCESS 方法,用來響應 action 中提交的對應型別事件;ALL_PRODUCTSstate.showLoader 設為 true,顯示載入狀態;ALL_PRODUCTS_SUCCESSaction 中提交的資料儲存到 state 中,並取消載入狀態;
  4. 最後新增了 actions 屬性,在 actions 屬性中定義了 allProducts 函式用於響應檢視層分發的對應型別的事件;我們首先提交了型別為 ALL_PRODUCTSmutation ,接著在 axios 請求成功後提交 ALL_PRODUCTS_SUCCESS,並附帶 products 資料體(payload

提示

我們可以看到 allProducts 方法中傳入了 { commit } 引數,這是採用瞭解構賦值的方式 const { commit } = context,避免後面使用 context.commit 過於繁瑣。

更新 ProductList 元件

再來看 src/components/products/ProductList.vue 檔案,我們對其做了修改,主要新增了生命週期函式 created,在該元件剛被建立時首先判斷本地 products 中是否有商品,如果沒有就向後端發起網路請求獲取資料。程式碼如下:

<template>
  <div>
    <div class="products">
      <div class="container">
        This is ProductList
      </div>
      <template v-for="product in products">
        <div :key="product._id" class="product">
          <!-- 其他欄位 -->
          <p class="product.manufacturer">生產廠商:{{product.manufacturer.name}}</p>
          <img :src="product.image" alt="" class="product__image">
          <button @click="addToCart(product)">加入購物車</button>
        </div>
      </template>
    </div>
  </div>
</template>

<!-- style -->

<script>
export default {
  name: 'product-list',
  created() {
    if (this.products.length === 0) {
      this.$store.dispatch('allProducts')
    }
  },
  computed: {
    // ...
  },
  methods: {
    // ...
  }
}
</script>
複製程式碼

注意到我們修改了兩個地方:

  • 調整模板中“生產廠商”欄位,把 {{product.manufacturer}} 修改為 {{product.manufacturer.name}}
  • 新增 created 生命週期方法,在該元件剛被建立時判斷 this.products.length === 0true 還是 false,如果是 true 則證明本地中還沒有任何商品,需要向後端獲取商品資料,於是通過 this.$store.dispatch 的方式觸發型別為 allProductsaction 中,在 action 中進行非同步操作,發起網路請求向後端請求商品資料並返回;如果是 false 則證明本地中存在商品,所以可以直接從本地獲取然後進行渲染。

最後我們也同樣需要調整一下 src/pages/Cart.vue 中的“生產廠商”欄位,修改其模板程式碼如下:

<template>
  <div>
    <div class="title">
      <h1>{{msg}}</h1>
    </div>
    <template v-for="product in cart">
      <div :key="product._id" class="product">
        <!-- 其他欄位 -->
        <p class="product.manufacturer">生產廠商:{{product.manufacturer.name}}</p>
        <img :src="product.image" alt="" class="product__image">
        <button @click="removeFromCart(product._id)">從購物車中移除</button>
      </div>
    </template>
  </div>
</template>

<!-- style -->

<!-- script -->
複製程式碼

同樣把 {{product.manufacturer}} 修改為 {{product.manufacturer.name}}

檢視效果

在測試這一步效果之前,首先確保 MongoDB 和後端 API 伺服器已經開啟。同時,如果你之前沒有在第二篇教程中測試過,很有可能你的資料庫是空的,那麼可以下載我們提供的 MongoDB JSON 資料檔案 manufacturers.jsonproducts.json,然後執行以下命令:

mongoimport -d test -c manufacturers manufacturers.json
mongoimport -d test -c products products.json
複製程式碼

然後再進入前端測試,你應該就可以看到從後臺獲取到的資料,然後同樣可以新增到購物車哦!

小結

在這一部分中我們學習瞭如何使用 Action 獲取遠端資料,並將獲取的資料提交到對應的 Mutation 中:

  • 首先我們需要匯入相關依賴:axiosAPI_BASE,由於發起網路請求。
  • 其次我們需要在 store 例項中新增 actions 屬性,並在 actions 屬性定義對應的方法,用於響應檢視層分發的對應型別的事件。
  • 在不同的方法中發起不同的網路請求,你是需要從後端獲取資料,還是修改後端資料等等。然後將後端響應的資料結果提交到對應型別的 mutation 中。

在下一篇教程中,我們將進一步探索 Vue 元件化,從而簡化頁面邏輯,並抽出 Getters 和 Mutation 資料邏輯。

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

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

相關文章