继续之前的,以下的总结有的是借鉴别人的,只是为了方便以后的学习:
1.vue1.0中的v-el和v-ref在2.0中被废弃了。所以2.0中如果需要此功能要通过ref属性写法不是ref:而是ref="名字",在js中用$refs获取元素。
首先了解一下ref:
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)只有当它插入文档后,才会变成真实的 DOM Vue 中所有的 DOM 变动都现在虚拟 DOM 上发生,然后在将实际变动发生的部分,反映在真实的 DOM 中,这种算法叫 DOM diff 可以极大的提高网页的性能但是,有时需要从组建获取真实的 DOM 节点,这时就要用到 ref 属性。
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子组件上,引用就指向组件实例:
_initScroll(){
//初始化scroll区域
this.menuScroll = new BScroll(this.$refs.menuWrapper, {
click: true //结合BScroll的接口使用,是否将click事件传递,默认被拦截了
});
this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
probeType: 3 //结合BScroll的接口使用,3实时派发scroll事件,探针的作用
});
//结合BScroll的接口使用,监听scroll事件(实时派发的),并获取鼠标坐标,当滚动时能实时暴露出scroll
this.foodsScroll.on('scroll', (pos) => { //事件的回调函数
this.scrollY = Math.abs(Math.round(pos.y));//滚动坐标会出现负的,并且是小数,所以需要处理一下,实时取得scrollY
}) }
其中betterSroll具体的请看链接:https://github.com/ustbhuangyi/better-scroll
2.nextTick()官方文档的说明是:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
nextTick()我的理解是获取更新后的DOM。异步更新。几个组件在DOM变化后都需要重新计算scrollY并更新scroll。只有先获取更新后的DOM,才能正常的使用滑动插件。此时nextTick()就是必须的。
看到一个更全面的解释贴上来:
什么时候需要用的Vue.nextTick()
你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,
原因是在created()钩子函数执行的时候DOM其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题。
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop)
当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。
这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。 如果此时你想要根据更新的 DOM状态去做某些事情,就会出现问题。
为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
created(){
this.classMap=['decrease','discount','guarantee','invoice','special'];
this.$http.get('/api/goods').then((response)=>{//'/api/goods'请求的是data.json下的goods数组
response=response.body;
if(response.errno===ERR_OK){
this.goods=response.data;
// console.log(this.goods)
this.$nextTick( ()=> {
this._initScroll();//调用初始化函数
})
}
})
},
vue中更改数据,DOM会跟着做映射,但vue更新DOM是异步的,用 $nextTick ()来确保Dom变化后能调用到_initScroll()方法。调用_initScroll()方法能计算内层ul的高度,当内层ul的高度大于外层wrapper的高度时,可以实现滚动
3.遍历取数据
{{item.name}}
classMap[item.type]是一个数组,通过item.type去取对应的class,item.type是data.json中mock的数据
-
左右两边联动
- 在vue实例生命周期的开始created函数,分别加载 _initScroll函数 和 _calculateHeight函数
*通过 _calculateHeight 计算foods内部每一个块的高度,组成一个数组listHeight
*在_initScroll里面,设置了bscroll插件的一个监听事件scroll,将food区域当前的滚动到的位置的y坐标设置到一个vue实例属性scrollY
this.scrollY = Math.abs(Math.round(pos.y));这是为了获取正值,因为有可能是负值。通过计算属性currentIndex,获取到food滚动区域对应的menu区域的子块的索引,然后通过设置一个class来做样式切换变化 :class="{'current':currentIndex === index} ,实现联动
另外当点击menu 区域的时候,会触发selectMenu事件,也会根据点击到的menu子块的索引然后去触发food区域滚动到对应的高度区块区间
this.foodsScroll.scrollToElement(el, 300);scrollToElement():是better-scroll中的方法,滚动到某个元素,el(必填)表示 dom 元素,time 表示动画时间,offsetX 和 offsetY 表示坐标偏移量,easing 表示缓动函数
*这样完成整个对应
_calculateHeight()方法计算各个右侧区间的高度:
_calculateHeight(){
let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); //获取每一个food的dom对象
let height = 0; this.listHeight.push(height); //初始化第一个高度为0
for (let i = 0; i < foodList.length; i++) {
let item = foodList[i]; //每一个item都是刚才获取的food的每一个dom
height += item.clientHeight; //主要是为了获取每一个foods内部块的高度,这是累积的高度
this.listHeight.push(height);//每个块的高度存放在数组中
}
}
}
_initScroll()方法为了实时获取scrollY值,
_initScroll:function(){
this.menuScroll=new BScroll(this.$refs.menuWrapper,{
click:true//结合BScroll的接口使用,是否将click事件传递,默认被拦截了
});//设置click:true使得左侧可以点击事件
//probeType:3 能够监听事件
//结合BScroll的接口使用,监听scroll事件(实时派发的),并获取鼠标坐标,当滚动时能实时暴露出scroll
this.foodsScroll=new BScroll(this.$refs.foodsWrapper,{
probeType:3//结合BScroll的接口使用,3实时派发scroll事件,探针的作用
});
//下面表示监听的是scroll滑动事件,返回pos对象
this.foodsScroll.on('scroll',(pos)=>{
this.scrollY=Math.abs(Math.round(pos.y));//实时拿到bsroll的y值
})
}
实时取得scrollY的值后,需要与左边进行映射,利用vue的计算属性:
computed:{
currentIndex(){//计算到达哪个区域的区间的时候的对应的索引值
for(let i=0;i=height1 && this.scrollY
此时这个计算currentIndex,返回到达哪个区域的区间的时候的对应的索引值,在html中通过当currentIndex === index时才设置current这个class,即: :class="{'current':currentIndex===index}"
-
{{item.name}}
-
{{food.name}}
{{food.description}}
月售{{food.sellCount}}
好评率{{food.rating}}
¥{{food.price}}¥{{food.oldPrice}}
左侧点击,联动右侧:
关于在selectMenu中点击,在pc界面会出现两次事件,在移动端就只出现一次事件的问题:
原因:bsScrooler会监听事件(例如touchmove,click之类),并且阻止默认事件(prevent stop),并且他只会监听移动端的,pc端的没有监听
在pc页面上 bsScroller也派发了一次click事件,原生也派发了一次click事件
//bsScroll的事件,有_constructed: true
MouseEvent {isTrusted: false, _constructed: true, screenX: 0, screenY: 0, clientX: 0…}
//pc的事件
MouseEvent {isTrusted: true, screenX: -1867, screenY: 520, clientX: 53, clientY: 400…}
解决:针对bsScroole的事件,有_constructed: true,所以做处理,return掉非bsScroll的事件
selectMenu(index, event){
if (!event._constructed) { //去掉自带的click事件点击,即pc端直接返回
return;
}
let foodsList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
let el = foodsList[index]; //类似jump to的功能,通过这个方法,跳转到指定的dom
this.foodsScroll.scrollToElement(el, 300);
},
总结以上:
1.ref="menuWrapper"获取到原生的dom
- this.$nextTick()一般在这之后dom才能渲染好,为了保证少的错误,操作原生的dom,在这之后在执行
3.一般写法先是将html框架下个大概,在进行css,最后进行dom操作
三、shopcart组件
首先再说一下组件:必须先引用然后在进行注册。
引用:
import shopcart from '../../components/shopcart/shopcart';
注册:就是在export default{}中进行注册
components:{
shopcart
}
也就是这样才可以在模板中使用组件:
其中1,2表示传递给组件中参数。
shopcart组件:
也是采用flex布局,右侧部分固定宽度(flex 0 0 105px),左边自适应宽度(flex 1)
采用固定定位,定位在底部(position fixed)
横向排列display:inline-block
包含购物车图标的div超出了父元素的高度,我们使用position:relative,并设置top为负来实现
box-sizing: border-box; 则div 设置的宽高将包含 边框及 padding
border-radius 50%,形成一个圆
选择了多少商品:定义成数组,底栏其余部分的变化都基于这个对象的变化而变化
selectFoods: {
type: Array, default() { return [{price: 20, count: 2}];
}
}
计算部分(都基于selectFoods进行相应计算)computed中的函数可以直接在Tempplate中以指针的形式引用
computed: {
totalPrice() {//计算总价,超过起送额度后提示可付款
let total = 0; this.selectFoods.forEach((food) => {
total += food.price * food.count;
}); return total;
},
totalCount() {//计算选中的food数量,在购物车图标处显示,采用绝对定位,top:0;right:0;显示在购物车图标右上角
let count = 0; this.selectFoods.forEach((food) => {
count += food.count;
}); return count;
}
控制底部右边内容随food的变化而变化,payDesc()控制显示内容,payClass()添加类调整显示样式
在template中 {{payDesc}}
在computed中:
payDesc() {
if (this.totalPrice === 0) {
return `¥${this.minPrice}元起送`; //这里使用的是es6中的反引号
} else if (this.totalPrice < this.minPrice) {
let diff = this.minPrice - this.totalPrice;
return `还差¥${diff}元起送`;
} else {
return '去结算'; //单引号,单引号和反引号不同
}
},
payClass() {
if (this.totalPrice < this.minPrice) {
return 'not-enough';
} else {
return 'enough';
}
}
总结:通过以上学习发现,selectFoods()的变化起着关键作用,它的变化会引起DOM的变化,并最终体现到界面上,而我们不用关注DOM内部的具体实现,这就是vue的一大好处