GoodsList组件用来展示商品列表,而单个商品对应的组件是GoodsListItem,前者是后者的容器。之前我们在实现类似结构时,会在item组件中定义几个插槽,然后在容器组件中对插槽进行填充。但这次由于item中的结构较为复杂,所以老师采用了另一种组件通信方式,即props。首先爷爷组件Home将请求到的30个商品的数据发送给父组件GoodsList,然后GoodsList在通过v-for动态生成GoodsListItem的同时,将每个商品的数据发送给各个GoodsListItem。在GoodsListItem中,以恰当的方式来显示每个商品的价格、图片、描述等信息。

 

先来看看GoodsListItem组件的结构:

<template>
  <div class="goods-item" @click="itemClick">
    <img :src="showImg" alt="" @load="imageLoad">
    <div class="goods-info">
      <p>{{goodsItem.title}}</p>
      <span class="price">{{goodsItem.price}}</span>
      <span class="collect">{{goodsItem.cfav}}</span>
    </div>
  </div>
</template>

当点击该item后,会触发itemClick方法,触发结果就是跳转到该商品的详情页面,通过修改当前路由来实现。具体我们在详情组件中再介绍。

itemClick(){
      this.$router.push('/detail/' + this.goodsItem.iid)
}

图片的src属性为一个计算属性showImg,这是因为在服务器返回的数据的存储结构中,图片的位置有两种可能。

showImg(){
      return this.goodsItem.image || this.goodsItem.show.img
}

图片加载完成后会触发imageLoad方法,众所周知,图片的加载是最慢的,很可能在scroll组件计算滚动区域高度时,图片还没加载完。当图片全部加载完后,页面实际高度为6000,而可滚动区域的高度只有5000,这就导致页面可能滚到一半就滚不下去了。所以我们有必要在所有图片加载完之后,让scroll组件刷新可滚动区域的高度。

但是注意到当前的GoodsListItem组件是Home的孙子组件,它们之间通信需要发送两次$emit,我们采用另一种方法:事件总线。

在main.js文件中,我们在Vue的原型上添加一个$bus属性,属性值为,,,一个vue实例:

Vue.prototype.$bus = new Vue()

之后我们在任意位置访问this.$bus都相当于在访问一个vue实例,而vue实例上可以通过$emit和$on来发送和监听自定义事件。如下:

//  GoodsListItem组件中
    imageLoad(){
this.$bus.$emit('homeItemImageLoad') // 事件总线发送事件 }
//    Home组件中  
    const refresh = debounce(this.$refs.scroll.refresh, 200);
    this.$bus.$on("homeItemImageLoad", () => {
      refresh();
    });    

由于我们给每个图片都绑定了imageLoad方法,所以很可能在短时间内,该方法会被触发30次(因为每次加载30张图片)。而我们只希望最后一张图片加载完成时刷新,因此这里使用了防抖函数debounce来提升性能,当refresh方法被调用时,必须在之后的200ms之内没有出现同样的调用,这时refresh才会真的被执行。

debounce方法的代码如下,这是面试时的必考题(还有节流函数throttle),必须烂熟于心。

function debounce(func, delay){
  let timer = null 
  return function(...args){
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

另外还要注意这里  和  轮播图加载完成的事件响应函数 的区别,轮播图由于多个图片和一张图片占用的高度相同,所以第一张图片加载完就发送了事件,之后的图片加载完成后,压根没有再发送事件。而这里父组件监听到事件后进入回调函数,防抖函数只是减少了该回调的调用次数,而子组件发送事件的次数,仍然为30次没有变。

scroll组件中的refresh方法其实是scroll对象自带的方法,这里进行了一层封装,使得在调用时可以少写一个.scroll。另外,因为scroll对象是在scroll组件的mounted()中初始化的,当在父组件中调用scroll的方法时,很可能scroll对象还未创建,方法压根不存在,便会报错。通过将方法名和方法调用进行逻辑与,可以保证只有当方法存在时才会被调用,避免报错。

    refresh() {
      this.scroll && this.scroll.refresh();
    },

还要注意的是,对事件总线上事件的监听,被写在了生命周期钩子mounted()里面。这是因为只有当组件都挂载完成以后,才能确保this.$refs可以正常使用;如果在created()中使用this.refs.xxx,很可能返回undefined。

posted on 2021-06-25 02:48  springxxxx  阅读(541)  评论(0编辑  收藏  举报