利用vue重构有赞商城的思路以及总结整理vue.js
以下是本次项目的代码链接和预览链接:
在完成了多页面应用的基础结构的搭建之后,会出现项目根目录下有一个src文件夹,src文件里有components、modules、pages三个文件夹的情况,而components文件夹是用来放置一些共用的vue组件的,而modules文件夹里是放置一些共用的css、js模块,至于最后的pages文件夹则是用来放置有赞商城的不同页面的文件,每个页面都会在pages内呈一个单独的文件夹,里面会放置关于这个页面的独有的所有文件。
在这里先说明一下,重构过程中所有获取到的数据,都是通过使用在easymock上编写对应的接口(原在数据在rap2上,但是接口数据不稳定且无法搭建在github上),然后通过axios发送异步请求来获取到的模拟的数据,这是模仿真实的开发环境下的操作,具体的实现过程的话可以参考easymock以及我在github上面的源码文件。
以下是重构有赞商城的所需的页面列表,一共有六个页面,分别为:
1.首页2.目录分类页3.商品搜索列表页4.商品详情页5.购物车页面6.个人中心地址管理页面接下来我们会逐个页面来说说他们的重构思路
然后我们会用到的一些第三方插件,分别为:
一、首页
1、无缝轮播组件
那我们首先来说一下轮播组件,首先我们需要在src目录下的compnents文件夹里新建一个轮播组件文件,轮播的话我们会直接选择使用swiper插件提供的轮播组件库,我们只需把它封装到一个组件文件中即可,具体的操作在这里我就不详细说明了,这里只强调两个需要注意的问题:
1.应不应该在轮播组件放入图片数据呢?
回答:不应该,原因是为了使得轮播组件独立出来,在不同的组件中得以复用,并且使其可以适应不同规格不同数量的图片,因此我们的轮播组件只负责展示数据,不负责拿数据,数据应该通过props从父组件中获取。
回答:首先我们需要了解的是swiper是对DOM节点进行操作的,所以swiper的配置应该写在组件的mounted生命周期钩子里,因为在这个阶段已经在页面上生成了该组件对应的DOM节点;另一方面,swiper组件里的数据是swiper的父组件异步获取后传递给swiper的,因此应该等swiper拿到了传递的数据之后再对这个组件进行渲染,因此需要给这个组件添加一个v-if="bannerLists"的判断,判断swiper组件是否获取到数据,只有获取到了数据才生成这个DOM节点。
同样的,这里有两个值得注意的问题:
1.获取到的价格的格式并不统一,如何来使得这些价格的格式统一起来?
回答:这里需要用到vue实例里的一个自带属性filters来对数据进行过滤,在vue1.0的时候,filters里面会有自带的过滤器,不过在vue2.0时被移除了,因此需要我们来自己写所需的过滤器的过滤方式:
filters:{currency(num){num=num+''letarr=num.split('.')if(arr.length===1){returnnum+'.00'}else{if(arr[1].length===1){returnnum+'0'}elsereturnnum}}}只有在渲染页面时,只要对你想进行的数据后加上该过滤器即可:
¥{{list.price|currency}}
2.如何做到下来商品列表就发送对应的请求来更新一页新的商品列表?
回答:这里我们使用到了mint-ui,一个移动端分页效果库,然后我们使用它文档上面对应的infinitescroll的api来达到我们想要的效果,具体代码如下:
getList(){if(this.allLoad)returnthis.loading=trueaxios.post(url.hostLists,{pageNum:this.pageNum,pageSize:this.pageSize}).then((response)=>{letcurrentList=response.data.listsif(currentList.length3.底部导航栏组件
底部导航栏和轮播组件一样,由于可以在其他地方进行复用,因此会把该组件放于components文件夹中,这里值得一提的是,底部导航栏组件由于点击不同的图标,它会跳转到不同的页面,因此会导致导航栏状态的重新加载,因此,若想要在不同的页面让导航栏呈现不同的状态,我们需要在跳转的时候传入对应的查询参数,然后在跳转到不同的页面时读取这个参数来呈现对应的不同的状态,具体的代码片段如下:
最后,由于在其他页面中,filters属性和底部导航栏组件都可以进行复用,所以这里我们利用mixins属性,来对filters属性和底部导航栏组件的注入进行打包,打包在一个js文件夹下的mixin.js文件中:
importFootnavfrom'components/FootNav.vue'letmixin={filters:{currency(num){num=num+''letarr=num.split('.')if(arr.length===1){returnnum+'.00'}else{if(arr[1].length===1){returnnum+'0'}elsereturnnum}}},components:{Footnav}}exportdefaultmixin当你的页面需要使用到该过滤器,或者底部导航栏时,只要对这个模块进行引入,并在mixins属性中添加它即可:
newVue({...mixins:[mixin]})二、目录分类页
商品搜索列表页同样的基本操作与首页和目录分类页类似,这里唯一不同的是,商品搜索列表页在用户下拉一定距离后,会出现一个回到顶部的图标,点击图标,用户就可以直接回到顶部,在这里,我们使用了一个叫作Velocity.js的动画库,它是把css中的一些动画效果进行封装,进而可以通过一些api轻松实现一些简单的动画效果,例如上面所说的回到顶部,在项目中的代码片段如下所示:
//引入VelocityimportVelocityfrom'velocity-animate/velocity.js'//在methods中加入对应方法methods:{scrollMove(){if(window.scrollY>=290){this.isShow=true}else{this.isShow=false}},goToTop(){Velocity(document.body,'scroll',{duration:500,easing:"easeOutQuart"});this.isShow=false//回到顶部图标消失}}四、商品详情页
在商品详情页中,除了有对数据的获取和页面的渲染外,这里主要涉及到了三个新的操作:
首先是sku算法,由于此次商品详情页的选择并不需要使用到它,因为商品的可选属性只有一个,但是在实际情况下,由于很多商品的可选属性不止一个,因此是需要使用到sku算法的。SKU=StockKeepingUnit(库存量单位),同一型号的产品,或者说是同一个产品项目(产品条形码是针对企业的产品)。
然后如何制作sku页面载入和消失时的动画效果呢?这里我们使用到了vue提供的自带transition的封装组件,可以通过这个组件来给任何元素和组件添加进入或者离开时的过渡。这个组件提供了八个JavaScript钩子函数以及六个过渡类名的切换,利用这些钩子函数以及类名的切换就可以完成组件的过渡动画了,这里列举一个vue文档上的典型例子给大家参考一下吧:
transition过渡:
Togglehello
newVue({el:'#demo',data:{show:true}}).fade-enter-active,.fade-leave-active{transition:opacity.5s;}.fade-enter,.fade-leave-to/*.fade-leave-activebelowversion2.1.8*/{opacity:0;}最后在遮罩层和弹出层出现之后(即sku页面),对页面进行上下拖动时,背后的内容层也会跟着一起拖动,这是典型的滚动穿透问题,在这里我的解决办法是监听遮罩层和弹出层的出现,当它们出现之后,我们更改内容层的样式,使其样式变为:position:fixed;width:100%;这样内容层就不会再滚动了,之后我们再通过设置:scrollTop=document.scrollingElement.scrollTopdocument.body.style.top=-scrollTop+'px'使得内容层的滚动高度与遮罩层和弹出层出现前相同,并且,调整html元素的样式:height:100%;overflow:hidden;,在关闭遮罩层和弹出层后,还原这些修改样式,即可使得滚动穿透的问题得以解决。需要注意的是,还原这些样式之后,原本内容层滚动的高度就会丢失,因此我们要通过之前记录下来内容层滚动的高度,在还原样式时将滚动高度也一并还原。
document.scrollingElement.scrollTop=scrollTop这样滚动穿透的问题就算是彻底解决了,下面是全部的这部分的全部代码片段:
五、购物车页面
购物车页面里面比较多的逻辑关系,在此就不一一枚举了,大概说一下它的重构思路:
商品的获取渲染以及增加是否被选中属性
获取后台数据加载处理或动态响应式处理
商品选中店铺选中全选,影响价格三级联动。
编辑状态,其余不可切换。对数量操作,加减更改。删除,单商品删除,选中(多个)删除,商品删除店铺删除。
原生事件,滑动删除页面,Volecity。
删除多个商品进行过滤处理
fetch层封装,
同一个场景下思维层封装
问题呈现,左滑删除样式继承。[0].style.left='0px'this.$refs[`goods-${shopIndex}-${goodIndex}`][0].style.left='0px'
ref是非响应式的,不建议在模板中进行数据绑定,即使用唯一标识绑定
v-for模式使用“就地复用”策略,简单理解就是会复用原有的dom结构,尽量减少dom重排来提高性能(解决方案:还原dom样式)
key为每个节点提供身份标识,数据改变时会重排,最好绑定唯一标识,如果用index标识可能得不到想要的效果(绑定唯一识别key)
网页性能管理详解
首先获取数据,渲染到页面这些是基本的操作
获取到数据之后,由于有一些属性数据中没有,并且我们想要它在页面中是呈响应式存在的,因此从接口获取到数据之后不应该直接赋值给data里,而是应该先给数据增添属性,再把增添后的数据赋值到data处,具体代码如下:
getLists(){cart.getCartLists().then((response)=>{letlist=response.data.cartListlist.forEach(shop=>{shop.checked=trueshop.editingStatus=falseshop.editingMsg='编辑'shop.removeChecked=falseshop.goodsList.forEach(good=>{good.checked=truegood.removeChecked=falsegood.touchDelete=false})})this.cartLists=list})}每次选择了商店下的商品时,都利用数组的every方法来遍历数组看是否全部商品都被选中了,若选中则商店也被选中,同理,若选择了商店,则遍历商店下的商品,把商店下的商品全部选中。取消选中亦是同理。
全选与否则利用计算属性来处理,利用计算属性的getter来获取此时购物车的状态来判断是否被全选,利用计算属性的setter来处理点击全选时商店及商店下商品的状态的改变。
同样的,利用计算属性来计算正常状态下选中商品的总价,并返回选中商品的列表。同理,利用计算属性来计算编辑状态下的选中的商品的列表,并以数组的形式返回。
在编辑状态下,我利用了计算属性来对商品的数量的数据进行了监测,若判断出数量中存在非数字或者负数的情况,则会自动把数量的数据变成1。
利用touchstart和touchend两个事件来实现商品左拉删除的功能,这两个事件分别绑定start和end的方法,方法的具体代码如下所示:
start(e,good){good.startX=e.changedTouches[0].clientX},end(e,good,goodIndex,shopIndex,shop){letendX=e.changedTouches[0].clientXletleft='0px'if(good.startX-endX>100){good.touchDelete=trueleft='-60px'Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left})shop.goodsList.forEach((otherGood,index)=>{if(otherGood.touchDelete&&index!==goodIndex){otherGood.touchDelete=falseVelocity(this.$refs[`goods-${shopIndex}-${index}`],{left:'0px'})}})}elseif(endX-good.startX>100){good.touchDelete=falseleft='0px'Velocity(this.$refs[`goods-${shopIndex}-${goodIndex}`],{left})}}当添加了左拉删除的功能之后,页面会出现一个BUG,就是左拉之后,点击该商品对应的商店下的编辑按钮,删除的按钮会继续被左拉,呈现一个比其他删除按钮长的BUG状态。
处理办法:通过给每个商品的一个具有唯一识别性的ref,然后在点击编辑时,找到已左拉的商品的对应的具有唯一识别性的ref,把它的左拉状态还原即可,具体代码如下所示:
shop.editingStatus=!shop.editingStatusif(shop.editingStatus){shop.goodsList.forEach((good,index)=>{if(good.touchDelete){good.touchDelete=falsethis.$refs[`goods-${shopIndex}-${index}`][0].style.left='0px'}})}六、个人中心地址管理页面
另外在这个页面中,我们使用到了vue-router和vuex,接下来我将会简要介绍它们在个人中心地址管理页面中的使用方式。
首先是vue-router,他是用于构建单页面应用的,是基于路由和组件,路由用于访问特定的路径,然后特定的路径与特定的组件相联系相映射,传统页面中,是通过超链接来实现页面的跳转和切换的,但在vue-router中,则是路由的切换,即组件的切换。
我们先来看看是如何配置一个routes、创建一个router实例并把它注入到vue实例中去的:
importVuefrom'vue'importRouterfrom'vue-router'Vue.use(Router)//构造配置letroutes=[{path:'/',components:require('../components/member.vue')},{path:'/address',components:require('../components/address.vue'),children:[{path:'',redirect:'all'},{path:'all',components:require('../components/all.vue')},{path:'form',name:'form',components:require('../components/form.vue')}]}]//创建router实例letrouter=newRouter({routes})exportdefaultrouterimportVuefrom'vue'importrouterfrom'./router'importstorefrom'./vuex'//根组件注入letview=newVue({el:'#app',router,store})//router-view标签作为配置路由后组件的容器