Vue踩坑实录(二)
在上一篇中说了一下踩过的前三个坑,剩下的坑就在这篇中全部搞定吧。
Vue踩坑实录(一)
- Vue-cli .js?.Vue?
- 父组件向子组件传值
- eslint format
- Vue中使用SCSS
- class绑定与顺序
- v-for 子组件DOM的取得
- 子组件向父组件的传递
- 数组的更新方法
Vue中使用SCSS
目前流行的CSS预出处理大大减少了书写CSS的不便性,自然这些在Vue中也是可以使用的。
Vue中使用SCSS的方法十分简单,只要在style
标签添加lang="scss"
即可。
<style lang="scss">
.picture-continer {
width: 320px;
height: 360px;
margin: auto;
padding: 40px 40px 0px 40px;
box-sizing: border-box;
border-radius: 1px;
background-color: #fff;
position: absolute;
cursor: pointer;
transform-style: preserve-3d;
transform-origin: 0 50% 0; // 改变变化的原点为x轴原点
transition: left .8s ease-in-out, top .8s ease-in-out, transform .5s;
perspective: 1800px;
h2 {
height: 80px;
margin: 0;
line-height: 80px;
color: #727272;
text-align: center;
font-size: 16px;
}
}
</style>
class绑定与顺序
在画廊应用中对于图片翻转的样式是通过CSS来控制,这一点通过使用Vue的class绑定可以很方便的完成。与React不同,连判断语句都不需要。
<template>
<figure class="picture-continer"
:style="{top: imgPos.position.top + 'px', left: imgPos.position.left + 'px', transform:'rotate(' + this.imgPos.rotate + 'deg)'}"
:class="{'is-center':imgPos.isCenter,'is-inverse':imgPos.isInverse}" // 绑定class,通过判断class开控制图片样式的显示
@click.stop="pictureAction">
<img :src="picture.src"
:alt="picture.title" />
<h2 class="img-title">{{picture.title}}</h2>
<div class="img-back">
<p>{{picture.desc}}</p>
</div>
</figure>
</template>
因此,在这里就需要注意。如果上面例子中的isCenter
和isInverse
同时都为true
的时候,此时添加在标签中class的顺序是与书写顺序相同的。
当两个class中有重复或者冲突的属性的时候,就要根据CSS的后书写的属性生效的规则来设计class的书写顺序。
如果在这边顺序对调的话,那么is-inverse
是不会生效的。即页面上的图片不会翻转。
.is-center {
transform: rotate(0deg);
z-index: 11;
}
.is-inverse {
transform: translate(320px) rotateY(180deg);
}
v-for 子组件DOM的取得
Vue中提供了v-for
指令能很方便的循环标签。在这次的应用中,图片和导航条都是通过循环单个组件生成的。
<template>
<div id="container"
class="stage">
<!-- 图片区域 -->
<section class="img-sec">
<picture v-for="(picture, index) in pictureList"
:key="index"
:index="index"
:picture="picture"
:img-pos="imgArrangeList[index]"
ref="pictureList"></picture>
</section>
<!-- 导航条区域 -->
<section class="nav-sec">
<navbar v-for="(picture, index) in pictureList"
:key="index"
:index="index"
:img-pos="imgArrangeList[index]"></navbar>
</section>
</div>
</template>
虽然说Vue主要通过数据驱动的方式来进行操作,不过还是会遇上要直接操作DOM的时候。
这个时候可以通过this.$el
来获取DOM的实体元素。
对于子组件来说,可以在mounted
方法中获取。
methods: {
setStageSize: function (sizeObj) {
var stage = this.$el; // 这里获取实体DOM
stage.style.width = sizeObj.width + 'px';
stage.style.height = sizeObj.height + 'px';
},
// 省略一些代码
mounted() {
this.pictureList = imgList;
this.initImgArrangeList(this.pictureList);
this.setStageSize(stageOpt); // 这个方法用到了实体DOM元素
// 省略一些代码
},
// 省略一些代码
}
注意点:
其实如果按照React那一版(请参照React 图片画廊 踩坑笔记)对于图片组件大小的设定,实际上是通过element.scrollWidth
和element.scrollHeight
的方式来设定的。由于React中,这些是写在render函数中的,因此可以通过ReactDOM.find()
的方法来获取。
而在Vue中,如果采用类似的方法,在mounted
或者updated
的$nextTick
方法中来获取DOM元素来进行设定的话,会造成死循环。
对此的情况,应该是Vue在对子组件进行渲染前需要获取到实体DOM,而此时实体DOM并没有生成,因此造成死循环。(个人的理解,有错误的地方还望指正)
所以最后的解决方法是修改数据结构,添加了图片组件的宽高信息,使得Vue在渲染前就能获取到从而成功渲染。(其实本来在CSS中也是写死的宽高)
子组件向父组件的传递
在上一篇中提到了Vue通过props
属性来向子组件传值。自然子组件也需要通过一定的方法向父组件进行通信。在React中,通过向子组件传入一个回调函数的方法(即子组件实际调用的仍是父组件的方法)来解决这个问题。
// 省略代码
// 利用rearrange函数
// 让被点击的图片剧中
center(index) {
return function() {
this.rearrange(index);
}.bind(this);
}
// 省略代码
// 向子组件传递事件
// 省略代码
PictureList.push( < Picture imgData = {imgData} key = {index} imgPos = {this.state.imgArrangeList[index]}
inverse = {this.inverse(index)}
center = {this.center(index)} // 往子组件中传递 center 事件
ref = {'imgData' + index} />);
// 省略代码
在子组件中通过this.props.center()
来调用。
// 省略代码
// 点击函数
handleClick = (event) => {
if (this.props.imgPos.isCenter) {
this.props.inverse();
} else {
this.props.center();
}
event.stopPropagation();
event.preventDefault();
}
// 省略代码
而在Vue中通过使用EventBus来进行事件的传递,类似于设计模式中的观察者模式。其中EventBus就是一个全局的Vue实例,在子组件中通过$emit
方法进行事件的发射,而在父组件中通过v-on
进行事件绑定。
子组件中通过emit
进行的事件设定。
// 省略代码
pictureAction: function () {
if (this.$props.imgPos.isCenter) {
// 通过$emit 来设置事件,可以传入对应的参数
bus.$emit('refresh-pic', this.$props.index);
} else {
bus.$emit('do-rearrange', this.$props.index);
}
}
// 省略代码
父组件通过v-on
进行事件的响应。
// 省略代码
mounted() {
this.pictureList = imgList;
this.initImgArrangeList(this.pictureList);
this.setStageSize(stageOpt);
this.setAreaPosition();
this.rearrange(0);
this.$nextTick(function () {
// 监听子组件传来的事件
bus.$on('do-rearrange', this.doRearrange);
bus.$on('refresh-pic', this.refreshPic);
});
},
// 省略代码
可以看出,无论Vue还是React,子组件实际上最后调用的还是父组件中的方法。
只不过在阅读层面上,Vue的观察者模式更容易读懂一点。不过这种通过EventBus的方法,只适用与小型的程序,当项目比较复杂时,还是应该使用Vuex来进行状态管理。
数组的更新方法
Vue最大的特色是数据的双重绑定。其中对于数组,在官方文档中给出了相关的注意事项。特别要注意下面的情况,因为在数组操作中,这样的用法是最方便也是最容易想到的。
由于 JavaScript 的限制, Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如: vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如: vm.items.length = newLengt
自然,这一次从React转到Vue的过程中也遇到了这个问题。并且还是在最关键的图片重排函数中。
React版本
// 省略代码
// 重新布局所有图片 centerIndex 是居中图片的索引
rearrange(centerIndex) {
let imgArrangeList = this.state.imgArrangeList;
let AreaPos = this.AreaPos;
let center = AreaPos.center;
let hPosRangeLeftRange = AreaPos.hPosRange.leftRange;
let hPosRangeRightRange = AreaPos.hPosRange.rightRange;
let hPosRangeTop = AreaPos.hPosRange.top;
let vPosRangeX = AreaPos.vPosRange.x;
let vPosRangeTopRange = AreaPos.vPosRange.topRange;
let imgTopArray = [];
let topImgNumber = Math.floor(Math.random() * 2); // 取上测区域的图片数量
// 取得居中图片
let centerImgArray = imgArrangeList.splice(centerIndex, 1);
centerImgArray[0].position = center;
centerImgArray[0].rotate = 0; // 中间图片不需要旋转
centerImgArray[0].isCenter = true;
// 取出上侧图片的信息
let topImgSpliceIndex = Math.floor(Math.random() * (imgArrangeList.length - topImgNumber));
imgTopArray = imgArrangeList.splice(topImgSpliceIndex, topImgNumber);
// 布局上侧图片
imgTopArray.forEach(function(img) {
img.position = {
left: getRandomValue(vPosRangeX[0], vPosRangeX[1]),
top: getRandomValue(vPosRangeTopRange[0], vPosRangeTopRange[1])
}
img.rotate = getRandomRange();
img.isCenter = false;
});
// 布局左右两侧图片
for(let i = 0, len = imgArrangeList.length, k = len / 2; i < len; i++) {
let hPosRangeTmp = null;
// 取布局的随机值
if(i < k) {
hPosRangeTmp = hPosRangeLeftRange;
} else {
hPosRangeTmp = hPosRangeRightRange;
}
// 这一部分在Vue中是不会进行数据更新的因此需要做转换
imgArrangeList[i].position = {
left: getRandomValue(hPosRangeTmp[0], hPosRangeTmp[1]),
top: getRandomValue(hPosRangeTop[0], hPosRangeTop[1])
}
imgArrangeList[i].rotate = getRandomRange();
imgArrangeList[i].isCenter = false;
}
// 重新合成数组
if(imgArrangeList && imgTopArray[0]) {
imgArrangeList.splice(topImgSpliceIndex, 0, imgTopArray[0]);
}
imgArrangeList.splice(centerIndex, 0, centerImgArray[0]);
this.setState({
imgArrangeList: imgArrangeList
});
}
// 省略代码
Vue版本
// 省略代码
rearrange: function (centerIndex) {
// 省略代码
// 布局左右两侧图片
for (let i = 0, len = imgArrangeList.length, k = len / 2; i < len; i++) {
// 设置一个临时变量,其结构与每一个 imgArrangeList[i]相同
let tmpItem = {};
let hPosRangeTmp = null;
// 取布局的随机值
if (i < k) {
hPosRangeTmp = hPosRangeLeftRange;
} else {
hPosRangeTmp = hPosRangeRightRange;
}
// 将原来的方法用Vue中对应的数组更新方法替代,为此需要设置一个临时对象
// 然后通过Vue给出的方法进行实现
// imgArrangeList[i].position = {
// left: getRandomValue(hPosRangeTmp[0], hPosRangeTmp[1]),
// top: getRandomValue(hPosRangeTop[0], hPosRangeTop[1])
// }
// imgArrangeList[i].rotate = getRandomRange();
// imgArrangeList[i].isCenter = false;
tmpItem = {
position: {
left: getRandomValue(hPosRangeTmp[0], hPosRangeTmp[1]),
top: getRandomValue(hPosRangeTop[0], hPosRangeTop[1])
},
rotate: getRandomRange(),
isCenter: false,
isInverse: imgArrangeList[i].isInverse
};
this.$set(imgArrangeList, i, tmpItem);
}
//省略代码
},
// 省略代码
至于上部和中间的图片由于本来就是通过splice
方法进行设定的,因此可以在Vue中进行正常的更新。同时也是因此发现了上面的问题的。
后记
对于这个项目,从代码量上来说,Vue和React差距并不大。不过由于.Vue文件分布相对清晰一点,在代码阅读方面还是比较容易读懂的。
此外,React和Vue在设计思考的角度上,其实差别还是蛮大的。React在感觉上更贴合我们一直以来的开发思路,上手React还是一件比较轻松容易的事情。而Vue则是通过数据来进行驱动,一切都要以数据为重。刚开始的时候,还是挺不习惯的。不过在踩过几个坑之后也渐渐开始接受起了这种思考方式。
这一次Vue的踩坑开始很愉快的。之后大概会学习一下全家桶中的Vuex和Vue-router吧。