vant4.0 正式发布了,分析其源码学会用 vue3 写一个图片懒加载组件!



this.options = {
  ListenEvents: listenEvents || DEFAULT_EVENTS,
}
/*
  * add or remove eventlistener
  * @param  {DOM} el DOM or Window
  * @param  {boolean} start flag
  * @return
  */
initListen(el, start) {
  this.options.ListenEvents.forEach((evt) =>
    (start ? on : off)(el, evt, this.lazyLoadHandler)
  );
}
复制代码

8.4.1 on、off 监听事件,移除事件

export function on(el, type, func) {
  el.addEventListener(type, func, {
    capture: false,
    passive: true,
  });
}
复制代码
// vant/packages/vant/src/lazyload/vue-lazyload/util.js
export function off(el, type, func) {
  el.removeEventListener(type, func, false);
}
复制代码

8.5 initIntersectionObserver 初始化

/**
 * init IntersectionObserver
 * set mode to observer
 * @return
 */
initIntersectionObserver() {
  if (!hasIntersectionObserver) {
    return;
  }

  this.observer = new IntersectionObserver(
    this.observerHandler.bind(this),
    this.options.observerOptions
  );

  if (this.listeners.length) {
    this.listeners.forEach((listener) => {
      this.observer.observe(listener.el);
    });
  }
}
复制代码

8.6 observerHandler 观测,触发 load 事件

mdn 文档:IntersectionObserverEntry

/**
 * init IntersectionObserver
 * @return
 */
observerHandler(entries) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      this.listeners.forEach((listener) => {
        // 如果加载完成了,就移除监听
        if (listener.el === entry.target) {
          if (listener.state.loaded)
            return this.observer.unobserve(listener.el);
          listener.load();
        }
      });
    }
  });
}
复制代码

8.7 实例方法 addLazyBox 添加要懒加载的组件到队列(数组)

/*
  * add lazy component to queue
  * @param  {Vue} vm lazy component instance
  * @return
  */
addLazyBox(vm) {
  this.listeners.push(vm);
  // 浏览器环境
  if (inBrowser) {
    // 把
    this.addListenerTarget(window);
    // 如果是监听 observer 模式,监听 new IntersectionObserver().observe(vm.el)
    this.observer && this.observer.observe(vm.el);
    if (vm.$el && vm.$el.parentNode) {
      // 加入父级
      this.addListenerTarget(vm.$el.parentNode);
    }
  }
}
复制代码

8.8 实例方法 removeComponent 移除组件

/*
  * remove lazy components form list
  * @param  {Vue} vm Vue instance
  * @return
  */
removeComponent(vm) {
  if (!vm) return;
  remove(this.listeners, vm);
  this.observer && this.observer.unobserve(vm.el);
  // 移除父级
  if (vm.$parent && vm.$el.parentNode) {
    this.removeListenerTarget(vm.$el.parentNode);
  }
  // 移除 window 元素
  this.removeListenerTarget(window);
}
复制代码

8.9 addListenerTarget 添加事件的目标元素

比如 window 等。

/*
  * add listener target
  * @param  {DOM} el listener target
  * @return
  */
addListenerTarget(el) {
  if (!el) return;
  let target = this.targets.find((target) => target.el === el);
  if (!target) {
    target = {
      el,
      id: ++this.targetIndex,
      childrenCount: 1,
      listened: true,
    };
    this.mode === modeType.event && this.initListen(target.el, true);
    this.targets.push(target);
  } else {
    target.childrenCount++;
  }
  return this.targetIndex;
}
复制代码

8.10 removeListenerTarget 移除事件的目标元素

/*
  * remove listener target or reduce target childrenCount
  * @param  {DOM} el or window
  * @return
  */
removeListenerTarget(el) {
  this.targets.forEach((target, index) => {
    if (target.el === el) {
      target.childrenCount--;
      if (!target.childrenCount) {
        this.initListen(target.el, false);
        this.targets.splice(index, 1);
        target = null;
      }
    }
  });
}
复制代码

9. 总结

大致流程:

  • 事件模式
1. 初始化在元素(比如是 window,但不一定是 window)添加监听滚动和其他相关事件
2. 使用 Element.getBoundingClientRect API 获取元素的大小及其相对于视口的位置,判断是否进入可视化区
3. 进入可视区触发 load 事件,将图片设置 src 真实的图片路径,从而自动加载图片
4. 离开销毁监听的事件、和移除绑定事件的元素
复制代码
  • observer 模式

主要是第二步用 IntersectionObserver API。

// 把 Vue 实例对象 this 添加到 lazy 实例中
lazyManager.addLazyBox(this);
// 执行 lazyLoaderHandler 函数(发现节点(元素)在视口 checkInView,触发 load 事件)
lazyManager.lazyLoadHandler();
// 移除组件
lazyManager.removeComponent(this);
复制代码

在 load 事件中,调用 loadImageAsync 函数。

const image = new Image();
image.src = xxx;
image.onload = () => {}
image.onerror = () => {}
复制代码

行文至此,我们就算分析完了 lazyload 组件

其中,有很多细节处理值得我们学习。 比如:

  • 监听事件,不仅仅是 scroll 事件,还有'scroll','wheel','mousewheel','resize','animationend','transitionend','touchmove'
  • 监听本身数组存起来了
  • 目标元素也用数组存起来了。

install 函数主要有以下实现:

  • 把 lazy 实例对象添加到全局上
  • 注册懒加载组件
  • 注册图片组件
  • 注册指令 lazy
  • 注册指令 lazy-container 没有分析。

但限于篇幅原因,组件源码还有指令部分没有分析。 感兴趣的小伙伴可以自行分析学习。

如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力

10. 加源码共读群交流

最后可以持续关注我@若川。我会写一个组件库源码系列专栏,欢迎大家关注。

我倾力持续组织了一年每周大家一起学习200行左右的源码共读活动,感兴趣的可以点此扫码加我微信 ruochuan12 参与

作者:若川
链接:https://juejin.cn/post/7171227417246171149
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答你的问题,以下是我提供的参考答案: 首先,在使用Vue3和Vant组件之前,你需要搭建好Vue3的开发环境,并且安装好Vant组件库。具体可以参考官方文档:https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart 其次,对于二手交易app的商品发布和商品交易页面,我建议你分别创建两个组件,分别为"ProductPublish"和"ProductTrade"。在这两个组件中,你可以使用Vant组件库中的表单组件、按钮组件、图片上传组件等,来实现页面的布局和功能。 以下是一个简单的参考代码,仅供参考: ProductPublish.vue: ``` <template> <van-form @submit="onSubmit"> <van-field v-model="title" label="商品名称" required></van-field> <van-field v-model="description" label="商品描述" type="textarea"></van-field> <van-uploader v-model="images" label="商品图片" multiple></van-uploader> <van-field v-model="price" label="商品价格" type="number" required></van-field> <van-button type="primary" native-type="submit">发布商品</van-button> </van-form> </template> <script> import { ref } from 'vue'; import { Toast } from 'vant'; export default { setup() { const title = ref(''); const description = ref(''); const images = ref([]); const price = ref(''); const onSubmit = () => { // TODO: 提交表单数据到后台 Toast.success('发布成功'); }; return { title, description, images, price, onSubmit, }; }, }; </script> ``` ProductTrade.vue: ``` <template> <div> <van-list> <van-cell v-for="item in products" :key="item.id" :title="item.title" :label="`¥${item.price}`" :thumb="item.images[0]"> <template #right-icon> <van-button type="primary" size="small" @click="onBuy(item)">购买</van-button> </template> </van-cell> </van-list> </div> </template> <script> import { reactive } from 'vue'; import { Toast } from 'vant'; export default { setup() { const products = reactive([ { id: 1, title: '二手手机', description: '全新未使用,原装配件', images: ['https://img.yzcdn.cn/upload_files/2020/07/23/FnL2u8kHkD1Z8X2X4h4GK8xKJbJf.jpg!thumb500'], price: 800, }, { id: 2, title: '二手电脑', description: 'i5处理器,8G内存,256G固态硬盘', images: ['https://img.yzcdn.cn/upload_files/2020/07/23/FnL2u8kHkD1Z8X2X4h4GK8xKJbJf.jpg!thumb500'], price: 3000, }, ]); const onBuy = (product) => { // TODO: 购买商品,跳转到支付页面等操作 Toast.success('购买成功'); }; return { products, onBuy, }; }, }; </script> ``` 需要注意的是,以上代码仅作为参考,实际使用时需要根据自己的需求进行修改和完善。同时,还需要与后台进行数据交互,实现完整的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值