本文由圖雀社區成員 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
在前面的教程中,咱們已經學習瞭如何使用 Vuex 進行狀態管理,如何使用 Action 獲取遠程數據以及如何使用 Mutation 修改本地狀態,實現了用戶修改客戶端數據的同時,同步更新後端數據,而後更新本地數據,最後進行從新渲染。ios
這一節咱們將進一步經過 Vue 組件化的思想簡化複雜的頁面邏輯。git
咱們打開 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
方法遍歷數組,判斷當前商品是否在購物車中,若是不在則 isAdding
爲 true
,建立加入購物車按鈕;若是在則 isAdding
爲 false
,建立從購物車移除按鈕。編程
對應的兩個按鈕添加了兩個點擊事件:addToCart
和removeFromCart
axios
addToCart
,咱們經過 this.$store.commit
的方式將包含當前商品的對象做爲載荷直接提交到類型爲 ADD_TO_CART
的 mutation
中,將該商品添加到本地購物車中。removeFromCart
,咱們也是經過this.$store.commit
的方式將包含當前商品id的對象做爲載荷直接提交到類型爲REMOVE_FROM_CART
的mutation
中,將該商品從本地購物車中移除。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
中。
有了 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
,在每一個子組件中展現對應的商品信息。
最後,咱們重構一波購物車組件 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 進行狀態管理是經過 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
屬性。
光說不練假把式,咱們來手擼幾個 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_ID
的action
中,這裏會進行異步操做從後端獲取指定商品,並將該商品提交到對應類型的mutation
中,就來到了下一步。mutations
中添加了PRODUCT_BY_ID
和PRODUCT_BY_ID_SUCCESS
屬性,響應指定類型提交的事件,將提交過來的商品保存到本地。getters
並在getters
中添加了allProducts
屬性和productById
方法,用於獲取本地數據。在allProducts
中獲取本地中全部的商品;在productById
經過傳入的id查找本地商品中是否存在該商品,若是存在則返回該商品,若是不存在則返回空對象。咱們先經過一個簡單的例子演示若是使用 Vuex Getters。打開後臺商品組件,src/pages/admin/Products.vue
,咱們經過屬性訪問的方式調用對應的 getter
屬性,從而獲取本地商品,代碼以下:
export default {
computed: {
product() {
return this.$store.getters.allProducts[0];
}
}
}
複製代碼
咱們經過this.$store.getters.allProducts
屬性訪問的方式調用對應getter
中的allProducts
屬性,並返回本地商品數組中的第一個商品。
接着開始實現商品詳情組件 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
組件。
有了商品詳情,咱們還須要進入詳情的連接。再次進入 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經過動態路由的方式傳遞到詳情頁。
修改商品列表組件 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
數組。
實現了 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
中進行本地狀態修改,這已經使咱們習慣的思路了。
最後咱們打開路由配置 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倉庫加星❤️哦