Vue.js专区前端开发那些事程序员

Vue踩坑实录(二)

2017-04-15  本文已影响0人  吃土的小此方

在上一篇中说了一下踩过的前三个坑,剩下的坑就在这篇中全部搞定吧。
Vue踩坑实录(一)


  1. Vue-cli .js?.Vue?
  2. 父组件向子组件传值
  3. eslint format
  4. Vue中使用SCSS
  5. class绑定与顺序
  6. v-for 子组件DOM的取得
  7. 子组件向父组件的传递
  8. 数组的更新方法

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>

因此,在这里就需要注意。如果上面例子中的isCenterisInverse同时都为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.scrollWidthelement.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吧。

上一篇下一篇

猜你喜欢

热点阅读