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

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

組件化和邏輯複用能幫助寫出簡潔易懂的代碼,隨着應用越寫越複雜,咱們有必要把視圖層中重複的邏輯抽成組件,以求在多個頁面中複用;同時對於 Vuex 端,Store 中的邏輯也會愈來愈臃腫,咱們有必要使用 Vuex 提供的 Getters 來複用本地數據獲取邏輯。在這篇教程中,咱們將帶領你抽出 Vue 組件簡化頁面邏輯,使用 Vuex Getters 複用本地數據獲取邏輯。css

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

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

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

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

使用 Vue 組件簡化頁面邏輯

在前面的教程中,咱們已經學習瞭如何使用 Vuex 進行狀態管理,如何使用 Action 獲取遠程數據以及如何使用 Mutation 修改本地狀態,實現了用戶修改客戶端數據的同時,同步更新後端數據,而後更新本地數據,最後進行從新渲染。ios

這一節咱們將進一步經過 Vue 組件化的思想簡化複雜的頁面邏輯。git

實現 ProductButton 組件

咱們打開 src/components/products/ProductButton.vue 文件,它是用於操做商品在購物車中狀態的按鈕組件,代碼以下:github

<template>
  <div>
    <button v-if="isAdding" class="button" @click="addToCart">加入購物車</button>
    <button v-else class="button" @click="removeFromCart(product._id)">從購物車移除</button>
  </div>
</template>

<script> export default { props: ['product'], computed: { isAdding() { let isAdding = true; this.cart.map(product => { if (product._id === this.product._id) { isAdding = false; } }); return isAdding; }, cart() { return this.$store.state.cart; } }, methods: { addToCart() { this.$store.commit('ADD_TO_CART', { product: this.product, }) }, removeFromCart(productId) { this.$store.commit('REMOVE_FROM_CART', { productId, }) } } } </script>
複製代碼

該組件經過 v-if 判斷 isAdding 是否爲 true 來決定建立加入購物車按鈕仍是從購物車移除按鈕。cart 數組是經過 this.$store.state.cart 從本地獲取的。在 isAdding 中咱們先令其爲 true,而後經過 cart 數組的 map 方法遍歷數組,判斷當前商品是否在購物車中,若是不在則 isAddingtrue,建立加入購物車按鈕;若是在則 isAddingfalse,建立從購物車移除按鈕。編程

對應的兩個按鈕添加了兩個點擊事件:addToCartremoveFromCartaxios

  • 當點擊加入購物車按鈕時觸發 addToCart,咱們經過 this.$store.commit 的方式將包含當前商品的對象做爲載荷直接提交到類型爲 ADD_TO_CARTmutation 中,將該商品添加到本地購物車中。
  • 當點擊從購物車移除按鈕時觸發removeFromCart,咱們也是經過this.$store.commit的方式將包含當前商品id的對象做爲載荷直接提交到類型爲REMOVE_FROM_CARTmutation中,將該商品從本地購物車中移除。

實現 ProductItem 組件

src/components/products/ProductItem.vue文件爲商品信息組件,用來展現商品詳細信息,而且註冊了上面講的按鈕組件,改變商品在購物車中的狀態,除此以外咱們還使用了以前建立好的ProductButton組件,實現對商品在購物車中的狀態進行修改。

  • 首先經過import ProductButton from './ProductButton'導入建立好的ProductButton組件。
  • 而後在components中註冊組件。
  • 最後在模板中使用該組件。

代碼以下:

<template>
  <div>
    <div 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.name}}</p>
      <img :src="product.image" alt="" class="product__image">
      <product-button :product="product"></product-button>
    </div>
  </div>
</template>

<script> import ProductButton from './ProductButton'; export default { name: 'product-item', props: ['product'], components: { 'product-button': ProductButton, } } </script>
複製代碼

能夠看到,咱們將父組件傳入的product對象展現到模板中,並將該product對象傳到子組件ProductButton中。

重構 ProductList 組件

有了 ProductButton 和 ProductItem,咱們即可以來重構以前略顯臃腫的 ProductList 組件了,修改 src/components/products/ProductList.vue,代碼以下:

<template>
  <div>
    <div class="products">
      <div class="container">
        This is ProductList
      </div>
      <template v-for="product in products">
        <product-item :product="product" :key="product._id"></product-item>
      </template>
    </div>
  </div>
</template>

<script> import ProductItem from './ProductItem.vue'; export default { name: 'product-list', created() { // ... }, computed: { // ... }, components: { 'product-item': ProductItem } } </script>
複製代碼

這部分代碼是將以前展現商品信息的邏輯代碼封裝到了子組件ProductItem中,而後導入並註冊子組件ProductItem,再將子組件掛載到模板中。

能夠看到,咱們經過this.$store.state.products從本地獲取products數組,並返回給計算屬性products。而後在模板中利用v-for遍歷products數組,並將每一個product對象傳給每一個子組件ProductItem,在每一個子組件中展現對應的商品信息。

重構 Cart 組件

最後,咱們重構一波購物車組件 src/pages/Cart.vue,也使用了子組件ProductItem簡化了頁面邏輯,修改代碼以下:

<template>
  <div>
    <div class="title">
      <h1>{{msg}}</h1>
    </div>
    <template v-for="product in cart">
      <product-item :product="product" :key="product._id"></product-item>
    </template>
  </div>
</template>

<script> import ProductItem from '@/components/products/ProductItem.vue'; export default { name: 'home', data () { return { msg: 'Welcome to the Cart Page' } }, computed: { cart() { return this.$store.state.cart; } }, components: { 'product-item': ProductItem } } </script>
複製代碼

這裏也是首先導入並註冊子組件ProductItem,而後在模板中掛載子組件。經過this.$store.state.cart的方式從本地獲取購物車數組,並返回給計算屬性cart。在模板中經過v-for遍歷購物車數組,並將購物車中每一個商品對象傳給對應的子組件ProductItem,經過子組件來展現對應的商品信息。

把項目開起來,查看商品列表,能夠看到每一個商品下面都增長了「添加到購物車」按鈕:

購物車中,也有了「移出購物車」按鈕:

盡情地買買買吧!

小結

這一節咱們學習瞭如何使用 Vue 組件來簡化頁面邏輯:

  • 首先咱們須要經過import的方式導入子組件。
  • 而後在components中註冊子組件。
  • 最後將子組件掛載到模板中,並將須要子組件展現的數據傳給子組件。

使用 Vuex Getters 複用本地數據獲取邏輯

在這一節中,咱們將實現這個電商應用的商品詳情頁面。商品詳情和以前商品列表在數據獲取上的邏輯是很是一致的,能不能不寫重複的代碼呢?答案是確定的。以前咱們使用 Vuex 進行狀態管理是經過 this.$store.state 的方式獲取本地數據,而在這一節咱們使用 Vuex Getters來複用本地數據的獲取邏輯。

Vuex容許咱們在 store 中定義「getter」(能夠認爲是 store的計算屬性)。就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。

Getter也是定義在 Vuex Store 的 getter 屬性中的一系列方法,用於獲取本地狀態中的數據。咱們能夠經過兩種方式訪問 getter,一個是經過屬性訪問,另外一個是經過方法訪問:

  • 屬性訪問的方式爲this.$store.getter.allProducts,對應的getter以下:
allProducts(state) {
    // 返回本地中的數據
    return state.products;
}
複製代碼
  • 方法訪問的方式爲this.$store.getter.productById(id),對應的getter以下:
productById: (state, getters) => id => {
      //經過傳入的id參數進行一系列操做並返回本地數據
      return state.product;
  }
複製代碼

咱們能夠看到Getter能夠接受兩個參數:stategettersstate就表示本地數據源;咱們能夠經過第二個參數getters獲取到不一樣的getter屬性。

定義 Vuex Getters

光說不練假把式,咱們來手擼幾個 getters。打開 src/store/index.js 文件,咱們添加了一些須要用到的 action 屬性、mutation 屬性以及這一節的主角—— getters。代碼以下:

// ...

export default new Vuex.Store({
  strict: true,
  state: {
    // ...
  },
  mutations: {
    // ...
    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: {
    // ...
    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,
        });
      })
    }
  }
});
複製代碼

這裏主要添加了三部份內容:

  • actions中添加了productById屬性,當視圖層經過指定id分發到類型爲PRODUCT_BY_IDaction中,這裏會進行異步操做從後端獲取指定商品,並將該商品提交到對應類型的mutation中,就來到了下一步。
  • mutations中添加了PRODUCT_BY_IDPRODUCT_BY_ID_SUCCESS屬性,響應指定類型提交的事件,將提交過來的商品保存到本地。
  • 添加了getters並在getters中添加了allProducts屬性和productById方法,用於獲取本地數據。在allProducts中獲取本地中全部的商品;在productById經過傳入的id查找本地商品中是否存在該商品,若是存在則返回該商品,若是不存在則返回空對象。

在後臺 Products 組件中使用 Getters

咱們先經過一個簡單的例子演示若是使用 Vuex Getters。打開後臺商品組件,src/pages/admin/Products.vue,咱們經過屬性訪問的方式調用對應的 getter 屬性,從而獲取本地商品,代碼以下:

export default {
  computed: {
    product() {
      return this.$store.getters.allProducts[0];
    }
  }
}
複製代碼

咱們經過this.$store.getters.allProducts屬性訪問的方式調用對應getter中的allProducts屬性,並返回本地商品數組中的第一個商品。

建立 ProductDetail 組件

接着開始實現商品詳情組件 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 文件中,咱們對其進行了修改,將模板中的商品信息用 Vue 原生組件 router-link 包裹起來,實現商品信息可點擊查看詳情。代碼以下:

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

該組件通過修改以後實現了點擊商品的任何一條信息,都會觸發路由跳轉到商品詳情頁,並將該商品id經過動態路由的方式傳遞到詳情頁。

在 ProductList 中使用 Getters

修改商品列表組件 src/components/products/ProductList.vue 文件,使用了 Vuex Getters 複用了本地數據獲取邏輯,代碼以下:

// ...

<script> import ProductItem from './ProductItem.vue'; export default { name: 'product-list', created() { // ... }, computed: { // a computed getter products() { return this.$store.getters.allProducts; } }, components: { 'product-item': ProductItem } } </script>
複製代碼

咱們在計算屬性products中使用this.$store.getters.allProducts屬性訪問的方式調用getters中的allProducts屬性,咱們也知道在對應的getter中獲取到了本地中的products數組。

建立 Detail 頁面組件

實現了 ProductDetail 子組件以後,咱們即可以搭建商品詳情我頁面組件 src/pages/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>
複製代碼

該組件中定義了一個計算屬性product,用於返回本地狀態中指定的商品。這裏咱們使用了this.$store.getters.productById(id)方法訪問的方式獲取本地中指定的商品,這裏的id參數經過this.$route.params['id']從當前處於激活狀態的路由對象中獲取,並傳入對應的getter中,進而從本地中獲取指定商品。

在該組件剛被建立時判斷當前本地中是否有該商品,若是沒有則經過this.$store.dispatch的方式將包含當前商品id的對象做爲載荷分發到類型爲productByIdaction中,在action中進行異步操做從後端獲取指定商品,而後提交到對應的mutation中進行本地狀態修改,這已經使咱們習慣的思路了。

配置 Detail 頁面的路由

最後咱們打開路由配置 src/router/index.js 文件,導入了 Detail 組件,並添加了對應的路由參數,代碼以下:

// ...
import Detail from '@/pages/Detail';

export default new Router({
  routes: [
    // ...
    {
      path: '/detail/:id',
      name: 'Detail',
      component: Detail,
    }
  ],
});
複製代碼

又到了驗收的環節,運行項目,點擊單個商品,能夠進入到商品詳情頁面,而且數據是徹底一致的:

小結

這一節中咱們學會了如何使用Vuex Getters來複用本地數據的獲取邏輯:

  • 咱們須要先在store實例中添加getters屬性,並在getters屬性中定義不一樣的屬性或者方法。
  • 在這些不一樣類型的getter中,咱們能夠獲取本地數據。
  • 咱們能夠經過屬性訪問和方法訪問的方式來調用咱們的getter

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

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

相關文章
相關標籤/搜索