从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)
2020/2/11 8:12:29
本文主要是介绍从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
组件化和逻辑复用能帮助写出简洁易懂的代码,随着应用越写越复杂,我们有必要把视图层中重复的逻辑抽成组件,以求在多个页面中复用;同时对于 Vuex 端,Store 中的逻辑也会越来越臃肿,我们有必要使用 Vuex 提供的 Getters 来复用本地数据获取逻辑。在这篇教程中,我们将带领你抽出 Vue 组件简化页面逻辑,使用 Vuex Getters 复用本地数据获取逻辑。
欢迎阅读《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列:
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(一)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(二)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(三)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(四)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)(也就是这篇)
如果你希望直接从这一步开始,请运行以下命令:
git clone -b section-five https://github.com/tuture-dev/vue-online-shop-frontend.git cd vue-online-shop-frontend
本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给??这篇文章点赞+Github仓库加星??哦~
使用 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
,代码如下:
<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
可以接受两个参数:state
和getters
,state
就表示本地数据源;我们可以通过第二个参数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_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>
该组件经过修改之后实现了点击商品的任何一条信息,都会触发路由跳转到商品详情页,并将该商品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的对象作为载荷分发到类型为productById
的action
中,在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仓库加星??哦
这篇关于从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南