Vue踩坑实录(二)

在上一篇中说了一下踩过的前三个坑,剩下的坑就在这篇中全部搞定吧。
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吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容

  • 苗青有点不相信自己的耳朵,无论从身体的高度还是宽度,春生都是略输一筹,但就是在这并不出众的体内,竟然蕴藏着如此巨大...
    西岭布衣阅读 255评论 0 3
  • 几分钟前,母亲发来视频,说是想看宝宝,平常说宝宝睡了便不再坚持,今日却是还想看看,后来才说邻居陈叔叔过世了,说这话...
    潇潇成谜阅读 91评论 0 0
  • 据说,现代铁路两条铁轨之间的标准距离是四英尺又八点五英寸。 那依据是什么呢?来由绝对颠覆你的认知。 因为早期的铁路...
    周愚阅读 1,441评论 0 0