元件化和邏輯複用能幫助寫出簡潔易懂的程式碼,隨著應用越寫越複雜,我們有必要把檢視層中重複的邏輯抽成元件,以求在多個頁面中複用;同時對於 Vuex 端,Store 中的邏輯也會越來越臃腫,我們有必要使用 Vuex 提供的 Getters 來複用本地資料獲取邏輯。在這篇教程中,我們將帶領你抽出 Vue 元件簡化頁面邏輯,使用 Vuex Getters 複用本地資料獲取邏輯。
歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(一)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(二)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(三)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(四)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(五)》(也就是這篇)
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(六)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(七)》
- 《 從零到部署:用 Vue 和 Express 實現迷你全棧電商應用(終篇)》
如果您覺得我們寫得還不錯,記得 點贊 + 關注 + 評論 三連,鼓勵我們寫出更好的教程?
使用 Vue 元件簡化頁面邏輯
在前面的教程中,我們已經學習瞭如何使用 Vuex 進行狀態管理,如何使用 Action 獲取遠端資料以及如何使用 Mutation 修改本地狀態,實現了使用者修改客戶端資料的同時,同步更新後端資料,然後更新本地資料,最後進行重新渲染。
這一節我們將進一步通過 Vue 元件化的思想簡化複雜的頁面邏輯。
實現 ProductButton 元件
我們開啟 src/components/products/ProductButton.vue
檔案,它是用於操作商品在購物車中狀態的按鈕元件,程式碼如下:
<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
方法遍歷陣列,判斷當前商品是否在購物車中,如果不在則 isAdding
為 true
,建立加入購物車按鈕;如果在則 isAdding
為 false
,建立從購物車移除按鈕。
對應的兩個按鈕新增了兩個點選事件:addToCart
和removeFromCart
- 當點選加入購物車按鈕時觸發
addToCart
,我們通過this.$store.commit
的方式將包含當前商品的物件作為載荷直接提交到型別為ADD_TO_CART
的mutation
中,將該商品新增到本地購物車中。 - 當點選從購物車移除按鈕時觸發
removeFromCart
,我們也是通過this.$store.commit
的方式將包含當前商品id的物件作為載荷直接提交到型別為REMOVE_FROM_CART
的mutation
中,將該商品從本地購物車中移除。
實現 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
,程式碼如下:
// ...
This is ProductList
</div>
<template v-for="product in products">
<product-item :product="product" :key="product._id"></product-item>
</template>
</div>
</div>
// ...
</style>
<script>
import ProductItem from './ProductItem.vue';
export default {
name: 'product-list',
created() {
// ...
return this.$store.state.products;
}
},
components: {
'product-item': ProductItem
}
}
</script>
複製程式碼
這部分程式碼是將之前展示商品資訊的邏輯程式碼封裝到了子元件ProductItem
中,然後匯入並註冊子元件ProductItem
,再將子元件掛載到模板中。
可以看到,我們通過this.$store.state.products
從本地獲取products
陣列,並返回給計算屬性products
。然後在模板中利用v-for
遍歷products
陣列,並將每個product
物件傳給每個子元件ProductItem
,在每個子元件中展示對應的商品資訊。
重構 Cart 元件
最後,我們重構一波購物車元件 src/pages/Cart.vue
,也使用了子元件ProductItem
簡化了頁面邏輯,修改程式碼如下:
// ...
<h1>{{msg}}</h1>
</div>
<template v-for="product in cart">
<product-item :product="product" :key="product._id"></product-item>
</template>
</div>
</template>
// ...
</style>
<script>
import ProductItem from '@/components/products/ProductItem.vue';
export default {
name: 'home',
data () {
// ...
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
可以接受兩個引數:state
和getters
,state
就表示本地資料來源;我們可以通過第二個引數getters
獲取到不同的getter
屬性。
定義 Vuex Getters
光說不練假把式,我們來手擼幾個 getters。開啟 src/store/index.js
檔案,我們新增了一些需要用到的 action
屬性、mutation
屬性以及這一節的主角—— getters
。程式碼如下:
// ...
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;
}
},
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: {
// ...
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,
});
})
}
}
});
複製程式碼
這裡主要新增了三部分內容:
- 在
actions
中新增了productById
屬性,當檢視層通過指定id分發到型別為PRODUCT_BY_ID
的action
中,這裡會進行非同步操作從後端獲取指定商品,並將該商品提交到對應型別的mutation
中,就來到了下一步。 - 在
mutations
中新增了PRODUCT_BY_ID
和PRODUCT_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>
<script>
import ProductButton from './ProductButton';
export default {
// ...
複製程式碼
該元件經過修改之後實現了點選商品的任何一條資訊,都會觸發路由跳轉到商品詳情頁,並將該商品id通過動態路由的方式傳遞到詳情頁。
在 ProductList 中使用 Getters
修改商品列表元件 src/components/products/ProductList.vue
檔案,使用了 Vuex Getters 複用了本地資料獲取邏輯,程式碼如下:
// ...
</div>
</template>
<script>
import ProductItem from './ProductItem.vue';
export default {
// ...
computed: {
// a computed getter
products() {
return this.$store.getters.allProducts;
}
},
components: {
// ...
複製程式碼
我們在計算屬性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的物件作為載荷分發到型別為productById
的action
中,在action
中進行非同步操作從後端獲取指定商品,然後提交到對應的mutation
中進行本地狀態修改,這已經使我們習慣的思路了。
配置 Detail 頁面的路由
最後我們開啟路由配置 src/router/index.js
檔案,匯入了 Detail
元件,並新增了對應的路由引數,程式碼如下:
// ...
import Home from '@/pages/Home';
import Cart from '@/pages/Cart';
import Detail from '@/pages/Detail';
// Admin Components
import Index from '@/pages/admin/Index';
// ...
name: 'Cart',
component: Cart,
},
{
path: '/detail/:id',
name: 'Detail',
component: Detail,
}
],
});
複製程式碼
又到了驗收的環節,執行專案,點選單個商品,可以進入到商品詳情頁面,並且資料是完全一致的:
小結
這一節中我們學會了如何使用Vuex Getters
來複用本地資料的獲取邏輯:
- 我們需要先在
store
例項中新增getters
屬性,並在getters
屬性中定義不同的屬性或者方法。 - 在這些不同型別的
getter
中,我們可以獲取本地資料。 - 我們可以通過屬性訪問和方法訪問的方式來呼叫我們的
getter
。
想要學習更多精彩的實戰技術教程?來圖雀社群逛逛吧。