项目工程概述
项目github仓库地址:peanutMallFollowCoderwhy
talk is cheap, show me your code
一下记录我跟随coderwhy老师开发mall商城应用中碰到的一些问题和需要注意的点。
碰到的问题
创建项目
在创建项目的时候,不能使用大写或者一些其他特殊字符
git remove add origin respostity url
:将远程仓库和本地仓库联系起来
划分目录结构:
1 | assets // 资源 |
在根目录下创建vue.config.js配置别名。
在根目录下创建代码规范文件.editorConfig
。
开发
tabBar
如果你配置了别名,那么dom中引用url的时候,一定要加上~
1 | <img src="~assets/img/tabbar/home.svg" slot="img-common-slot"/> |
要不然就获取不到相应的图片。
设置点击之后active的样式
1 | <template> |
navBar
注意:插槽如果有多个,要使用具名插槽,在使用的时候也要指定替换哪一个插槽,效果图如下
封装网络请求
我们知道axios是第三方框架,如果有那一天这个第三方框架不更新的话,我们如果在项目中多处使用了axios,代码的重构就会带来很多不必要的麻烦。
①下载axios插件
1 | npm install axios |
②使用的时候不需要Vue.use命令install
直接导入axios即可使用,详情课参照官方文档 axios官方文档
github xowen
出现的问题:在传回axios实例之后,发现调用then方法失败,原因是:应当返回axiosInstance(config)配置过后的axiosInstance
错误代码:
1 | import Axios from "axios"; |
原因是因为axios实例配置过后的实例返回的是一个promise,所以可以调用then方法
正确代码:
1 | import Axios from "axios"; |
封装轮播图
注意:在每次滚动之后,都要验证位置,如果位置超过了图片的长度
开发FeatureView
点击FeatureView图片之后跳转的超链接
https://act.mogujie.com/zzlx67
TabControl
如果只是文字不一样的话,就没有必要用插槽
取而代之:
当需要设置点击active的样式,使用currentIndex标记当前所处于的标签,使用循环中的index和currentIndex判断是不是相等,如果相等,就是处于活跃的标签
其中定义的变量
--color-high-text
:文字高亮颜色
--color-tint
:下划线高亮
如果希望滚动的时候,停留在页面的顶端,可以设置css
在没有达到top:xxpx之前,position的属性默认是一个static,当达到xxpx时,浏览器会自动将posiotion的属性改成fixed
(如果需要适配ie浏览器,这个属性就不能随便用了)
1 | { |
商品列表页
create函数中些主要逻辑,主要是实现的方法在methods中执行
push方法可以传入可变参数,如果希望把一个数组push到另一个数组中,可以使用数组1.push(…数组2)
关于样式调整:
flex-wrap:wrap
根据子元素的宽度适配每一行占多少个子元素。
justify-content:space-around
:子元素占据每一行并均等分,但是中间的宽度是左右宽度的2倍
切换点击的类型对应现实的商品
注意的问题是,子组件向父组件传递参数的方法:
子组件:
1 | setActive(index){ |
父组件(其中,父组件用来接收子组件传递的方法的那个方法中不需要携带index形参):
1 | <tab-control class="tab-control" :titles="['流行','新款','精选']" @tabClick="tabClick"/> |
better-scoll的安装和使用
使用原生的滚动(内容多余整个屏幕的时候),在移动端滚动的时候会非常的卡顿,以前为了适配移动端滚动的问题,使用了iscoll
框架,但是现在他没有更新了,所以我们使用国内开发的一个better-scoll
安装
1 | npm install better-scroll --save |
如果想实现原生的局部js滚动,可以使用如下方式,但是前提是必须给父标签一个固定的高度才行。
第三方的框架, 要注意封装,万一他不更新,就凉凉了,下次如果项目中有很多地方使用到了这个框架,改起来很要命的
使用
上述使用是错误的,因为这个方法会在创建组建的时候被调用,这时候还没有挂载template,所以一直拿不到content。
在使用的时候,根据官方文档的说明,必须在管理的标签外面加一层wrapper
,并且管理的内容必须是一个标签
如果向监听用户滚动到哪一个位置了:并对当前状态添加一些业务功能
但是默认情况下,Bscroll是不能实时监听滚动的位置的,如果想要实时监听滚动的化,必须给他传参数
参数:
①probeType:是否侦测。0,1都是不侦测;2表示在手指滚动的过程中侦测,手指离开后的惯性滚动过程不侦测;3表示只要是滚动都侦测。(配合scroll
事件使用)
②click:默认值位false,在wrapper
中管理的content
如果有监听click事件,默认是不会生效的,如果将click属性改为true,才会传派事件。
③pullUpload:上拉加载更多(配合pullingUp
使用),但是当前页面只能调用一次上拉加载的回调函数,需要在最后调用一个finishPullUp
方法,结束当前上拉加载,才能监听下一次上拉加载。
封装better-scroll:封装一个vue文件,这个vue文件对better-scroll有依赖,其他的vue文件只要引用这个vue文件就可以了。
使用document.query获取wrapper
的方式不是特别好,如果在其他组件中也有class为wrapper
的标签,那么获取的时候就会有一些问题(query获取的是同一个页面上的第一个查询到的元素),在vue开发的时候经常出现类似的这种问题,值得注意,推荐的方法:
在vue中给标签绑定一个ref属性
1 <div ref="aaa"></div>之后再下面就可以通过this.$refs.aaa拿到上述标记的标签,但是在前面所讲的ref是在父组件中给子组件标记,然后拿到相应的子组件的方法。这里标记普通的标签也是可行的。
在给组件中的元素添加样式的时候,如果style标签中没有加上scoped属性,那么这个style中设置的样式就是全局的,例如
1 | <style scoped> |
为某个div设置高度
1 | { |
可以动态计算高度,也可以使用定位
出现的问题
然而在我加入better-scroll之后,页面上的元素都动不了了,就好像里面没有东西一样,这就很奇怪了,我封装的scroll应该没有问题(因为我在分类中测试了),问题应该处在home页的css上面,但是并不知道哪里的问题。
上百度看了,滚动不了的问题无非是wrapper
的高度要比content
的低,content
下只能有一个子标签,这些问题我都注意了,但是还是不管用,无能为力。
但是反复测试,在pc端换设备测试的时候发现,换来换去怎么又好使了?这也太玄学了吧。看来better-scroll插件还不是很稳定。
想到可能是版本太高?所以就换了个低版本,1.15.2(和coderwhy一样的版本测试),果然,换了版本之后就好用了。
backtop
如果想要监听组件的点击,需要添加修饰符native
给子组件添加ref属性,可以直接访问子组件中的内容。
上拉加载更多
在完成上拉加载更多之前,还需要解决滚动的bug,在滚动的时候,因为图片是动态加载的,所以vue的template会先计算出一个高度,这时候因为图片还没有加载,所以高度没有加载了图片的时候那么高,所以当图片加载完成之后,可滚动的区域就比之前的更小,所以滚动到某个区域发现会滚动不了了,所以这里先解决一些这个问题(有时候能滚动到底部,有时候不能,用户不友好,这是因为图片加载的是异步的)
better-scroll对象中有一个属性记录了可滚动的高度有多高,这个计算出来的高度就放在这个属性中
解决:当加载完所有的图片之后,最后在调用scroll对象的refresh方法,但是问题就在于,我怎么知道什么时候加载完了图片?
监听每一张图片是否加载完成。只要有一张图片加载完了,就更新scroll(refresh方法)
原生的监听图片时候加载完成使用的是
1 img.onload = function(){}但是vue已经帮我们封装了,只要给出一个imageLoad方法并实现就可以了
注意的是:我们这个img标签是在goodItem中的,这里我们不能直接拿到scroll对象:
①可以添加一个Vuex对象,在goodListItem中监听某个状态,如果这个状态改变,就在主页中执行refresh方法
②事件总线:和Vuex很像,但是他不是管理状态的,而是管理事件的:通过this.$bus.$emit(“funName”)发送一个事件给事件总线,然后再别的组件中通过this.$bus.$on(“funName”,function(){})监听别的组件中是否发射了一个叫funName的事件,但是默认情况下,程序中是没有$bus的,这个需要自己添加进来,使用vue原型添加进来
这样的话,就可以通过这个Vue实例发射事件
注意:
做完一次上拉加载更多,加载数据之后,不要忘了调用scroll对象中的finishPullUp方法。
有时候会出现报错的情况,调用refresh的方法的时候出错,原因可能是请求图片数据的方法在home.vue中的created中执行的,这时候可能请求太快了,但是goodListItem中的scroll没有创建出来,可能goodListItem中的组件还没有挂载,解决方法:在使用scroll之前,先判断scroll是不是为null,如果不为null才执行后面的代码
当然,改了之后还是有问题,之后又把this.$bus.$on方法放在了mounted中监听,但是发现,在分类和首页快速切换的时候还是会有问题.解决
每次在首页使用scroll的时候,在前面加上this.$refs.scroll的判断
优化
因为图片加载几乎是同时的,所以scroll.refresh方法执行的频率特别高,一秒钟之内如果你多次调用的话,执行一次就可以了.
防抖动(debounce):比如输入框,根据你每次输入的文字展示相应的内容,如果每次输入一个单词就向服务器发送一次请求,对服务器的压力就太大了,比如在输入一个字符之后,等待500ms,如果有下一次输入,那么就取消上一次的请求,将下一次输入和上一次输入加在一起……依次类推.这里就可以使用防抖动。
当不传入delay参数的时候,函数也没有执行三十次,这是为什么?
setTimeout函数在下一次事件循环的时候才执行,所以setTimeout中的函数永远是放在后面执行的
并且,防抖动的功能性函数,不要写在某一个组件里面,单独放在某个位置
增加防抖动之后,发现在内部调用apply方法的时候,浏览器报错apply未定义?
tabControl
加入better-scroll之后,原来tabControl的吸顶效果就不起作用了
首先获取tabControl的offsetTop,之后根据这个offsetTop来实现滚动到什么位置之后,开始吸顶(改变样式为fixed)
但是组件本身没有offsetTop属性,所以需要拿到template里面的标签才能获取 ,所有的组件都有一个$el属性,用来获取组件中的元素
通过这样的方式,发现它的高度远远小于我们想要的offsetTop,原因是图片还没有加载,获取的这个高度,是图片还没有撑起来的高度,所以就非常的矮,所以需要等图片全部加载完之后,在计算出来一个最终的offsetTop才是正确的。
在better-scroll中设置tabControl的样式是行不通的,所以使用了第二种方法:
增加一个一摸一样的better-scroll在顶部,实时监听滚动,如果滚动到了某个位置,就将顶部的tabControl显示,并且要注意的是,两个tabControl是独立的,所以在监听tabControl点击的时候,还要同步他们的currentIndex
路由状态保持
在跳转到其他页面的时候,首页的组件会销毁,这时候我们不希望他销毁。
在路由的
详情页开发
标题
标题因为已经封装了组件,所以直接使用就可以,点击标签为active,使用currentIndex
轮播图
因为轮播图中使用了query方法,所以组件外部的class不能和swiper里面的相同
在跳转的时候,关于keep-alive的问题,需要排除Detail页面,需要注意的是,exculde和include两个属性都是vue2.1+添加进来的属性,所以需要保证vue的版本符合要求。并且在exculde中设置的值要和各个组件中的name保持一致,要不然会找不到组件
商品信息
v-for() :可以遍历数字n,从1遍历到10
1 | <div v-for="n in 10"> |
如何判断对象是空的?
1 | Object.keys(obj).length === 0 |
如果两个组件中有相同代码,想做抽取的话,需要用到mixin
因为接口问题,所以这里只以商品的详情和参数为例
标题和内容的联动
父组件给子组件传值的时候,不是值到了,就渲染,而是会等一下执行一个update生命周期函数,如果想在数组到了之后就拿到组件中的offsetTop是不可能的,可以使用
1 | this.$nextTick(() => { |
this.$nextTick传进去的回调函数会在子组件被渲染完成之后被回调
但是在这个回调函数回调的时候,当前的DOM中是不包含图片的,所以就会出现高度不正确的问题(一般offsetTop值不对的时候,都是因为图片没有加载完)
可以在图片一加载就设置最新的值,但是调用的太频繁了,所以使用防抖
最终解决:
但是这里的话,我只有商品的参数信息和goods信息,对于上述所说的防抖函数都用不上,因为图片在goods的下面加载,然而我又没有添加goods的titleNavtext
1 | this.$nextTick(() =>{ |
1 | // 根据滚动改变标题的active |
底部工具栏
封装组件
1 | <template> |
backTop混入封装
使用mixins,直接默认导出文件,到时候在某个组件中使用就可以了
购物车
这里使用到了vuex插件,如果点击的是同一个商品,这时候不希望添加多次,只需要增加这个商品在cartList中的数量即可
1 | const store = new Vuex.Store({ |
mutations的作用就是为了修改state状态,尽可能实现的功能单一一点,上面的解决方案没有达到单一的实现原则,上述又判断逻辑的东西,最好放在actions中完成(不止是异步操作可以放在action中)
注意:在封装mutations中的常量出去的时候,不能使用对象的形式,应该使用下面的形式
1 | export const ADD_COUNTER = 'add_counter' |
导航栏
mapGetters的使用:
购物车页面滚动的问题,在添加完新的商品信息之后,回到商品信息页面应该对scroll刷新一遍,要不然scroll计算出来的高度仍然是添加之前的高度
修改item的状态
item的checked属性应该默认是放在对象模型中实现的,一旦用户点击按钮,取消选中状态(默认是选中的)
toast的封装
将封装的toast组件封装到一个插件中,然后通过安装插件的方式来使用toast
1 | import Toast from "./Toast"; |
补充细节
300ms延迟
移动端点击事件的300ms延迟,使用fastClick插件减少延迟
1 | // 安装 |
图片懒加载
github地址: vue-lazyload
px2vw-css单位转换插件
为了适配某些设备,需要做到固定的效果,这时候就可以使用别的单位,但是项目中往往会有很多地方需要调整单位,这时候就可以使用插件
安装插件:postcss-px-to-viewport,并且在vue.config中并不需要做额外的配置(其中px2rem也有相关的插件)
一般项目的设计稿都是基于iPhone6(750*1334),并且一个retina 1个点,相当于两个像素,适配的话,就是相对于iPhone6做适配