Vue踩坑实录(二)

作者: 吃土的小此方 | 来源:发表于2017-04-15 17:33 被阅读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吧。

    相关文章

      网友评论

        本文标题:Vue踩坑实录(二)

        本文链接:https://www.haomeiwen.com/subject/owjbattx.html