在微信小程序中实现virtual-list

背景

小程序在很多场景下面会遇到长列表的交互,当一个页面渲染过多的wxml节点的时候,会造成小程序页面的卡顿和白屏。原因主要有以下几点:

  1. 列表数据量大,初始化setData和初始化渲染列表wxml耗时都比较长;
  2. 渲染的wxml节点比较多,每次setData更新视图都需要创建新的虚拟树,和旧树的diff操作耗时比较高;
  3. 渲染的wxml节点比较多,page能够容纳的wxml是有限的,占用的内存高。

微信小程序本身的scroll-view没有针对长列表做优化,官方组件recycle-view就是一个类似virtual-list的长列表组件。现在我们要剖析虚拟列表的原理,从零实现一个小程序的virtual-list。

实现原理

首先我们要了解什么是virtual-list,这是一种初始化只加载「可视区域」及其附近dom元素,并且在滚动过程中通过复用dom元素只渲染「可视区域」及其附近dom元素的滚动列表前端优化技术。相比传统的列表方式可以到达极高的初次渲染性能,并且在滚动过程中只维持超轻量的dom结构。

虚拟列表最重要的几个概念:

  • 可滚动区域:比如列表容器的高度是600,内部元素的高度之和超过了容器高度,这一块区域就可以滚动,就是「可滚动区域」;

  • 可视区域:比如列表容器的高度是600,右侧有纵向滚动条可以滚动,视觉可见的内部区域就是「可视区域」。

实现虚拟列表的核心就是监听scroll事件,通过滚动距离offset和滚动的元素的尺寸之和totalSize动态调整「可视区域」数据渲染的顶部距离和前后截取索引值,实现步骤如下:

  1. 监听scroll事件的scrollTop/scrollLeft,计算「可视区域」起始项的索引值startIndex和结束项索引值endIndex;
  2. 通过startIndex和endIndex截取长列表的「可视区域」的数据项,更新到列表中;
  3. 计算可滚动区域的高度和item的偏移量,并应用在可滚动区域和item上。

在这里插入图片描述

1.列表项的宽/高和滚动偏移量

在虚拟列表中,依赖每一个列表项的宽/高来计算「可滚动区域」,而且可能是需要自定义的,定义itemSizeGetter函数来计算列表项宽/高。

itemSizeGetter(itemSize) {
   
      return (index: number) => {
   
        if (isFunction(itemSize)) {
   
          return itemSize(index);
        }
        return isArray(itemSize) ? itemSize[index] : itemSize;
      };
    }
复制代码

滚动过程中,不会计算没有出现过的列表项的itemSize,这个时候会使用一个预估的列表项estimatedItemSize,目的就是在计算「可滚动区域」高度的时候,没有测量过的itemSize用estimatedItemSize代替。

getSizeAndPositionOfLastMeasuredItem() {
   
    return this.lastMeasuredIndex >= 0
      ? this.itemSizeAndPositionData[this.lastMeasuredIndex]
      : {
    offset: 0, size: 0 };
  }

getTotalSize(): number {
   
    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();
    return (
      lastMeasuredSizeAndPosition.offset +
      lastMeasuredSizeAndPosition.size +
      (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize
    );
  }
复制代码

这里看到了是直接通过缓存命中最近一个计算过的列表项的itemSize和offset,这是因为在获取每一个列表项的两个参数时候,都对其做了缓存。

 getSizeAndPositionForIndex(index: number) {
   
    if (index > this.lastMeasuredIndex) {
   
      const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();
      let offset =
        lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size;

      for (let i = this.lastMeasuredIndex + 1; i <= index; i++) {
   
        const size = this.itemSizeGetter(i);
        this.itemSizeAndPositionData[i] = {
   
          offset,
          size,
        };

        offset += size;
      }

      this.lastMeasuredIndex = index;
    }

    return this.itemSizeAndPositionData[index];
 }
复制代码

2.根据偏移量搜索索引值

在滚动过程中,需要

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值