Skip to content
weapp custom component -- recycle-view
JavaScript
Branch: master
Clone or download
cunjinli
Latest commit b3b0071 Sep 9, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
images fix eslint and 数据跳动的bug Aug 24, 2018
src Fix #32 #29 Sep 6, 2019
test upgrade framework Aug 23, 2018
tools fix #1 支持一个页面多个长列表 Aug 24, 2019
.babelrc upgrade framework Aug 23, 2018
.eslintrc.js upgrade framework Aug 23, 2018
.gitignore first commit Jul 24, 2018
.npmignore up Aug 24, 2018
LICENSE Initial commit Jul 24, 2018
README.md 去掉placeholderClass Sep 9, 2019
gulpfile.js update tools and src Jul 26, 2018
package.json Fix #32 #29 Sep 6, 2019

README.md

recycle-view

小程序自定义组件

使用此组件需要依赖小程序基础库 2.2.2 版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档

背景

​ 电商小程序往往需要展示很多商品,当一个页面展示很多的商品信息的时候,会造成小程序页面的卡顿以及白屏。原因有如下几点:

  1. 商品列表数据很大,首次 setData 的时候耗时高
  2. 渲染出来的商品列表 DOM 结构多,每次 setData 都需要创建新的虚拟树、和旧树 diff 操作耗时都比较高
  3. 渲染出来的商品列表 DOM 结构多,占用的内存高,造成页面被系统回收的概率变大。

因此实现长列表组件来解决这些问题。

实现思路

​ 核心的思路就是只渲染显示在屏幕的数据,基本实现就是监听 scroll 事件,并且重新计算需要渲染的数据,不需要渲染的数据留一个空的 div 占位元素。

​ 假设列表数据有100个 item,知道了滚动的位置,怎么知道哪些 item 必须显示在页面?因为 item 还没渲染出来,不能通过 getComputedStyle 等 DOM 操作得到每个 item 的位置,所以无法知道哪些 item 需要渲染。为了解决这个问题,需要每个 item 固定宽高。item 的宽高的定义见下面的 API 的createRecycleContext()的参数 itemSize 的介绍。

​ 滚动过程中,重新渲染数据的同时,需要设置当前数据的前后的 div 占位元素高度,同时是指在同一个渲染周期内。页面渲染是通过 setData 触发的,列表数据和 div 占位高度在2个组件内进行 setData 的,为了把这2个 setData 放在同一个渲染周期,用了一个 hack 方法,所以定义 recycle-view 的 batch 属性固定为batch="{{batchSetRecycleData}}"

​ 在滚动过程中,为了避免频繁出现白屏,会多渲染当前屏幕的前后2个屏幕的内容。

包结构

长列表组件由2个自定义组件 recycle-view、recycle-item 和一组 API 组成,对应的代码结构如下

├── miniprogram-recycle-view/
    └── recycle-view 组件
    └── recycle-item 组件
    └── index.js

包结构详细描述如下:

目录/文件 描述
recycle-view 组件 长列表组件
recycle-item 组件 长列表每一项 item 组件
index.js 提供操作长列表数据的API

使用方法

  1. 安装组件
npm install --save miniprogram-recycle-view
  1. 在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置

    {
      "usingComponents": {
        "recycle-view": "miniprogram-recycle-view/recycle-view",
        "recycle-item": "miniprogram-recycle-view/recycle-item"
      }
    }
  2. WXML 文件中引用 recycle-view

    <recycle-view batch="{{batchSetRecycleData}}" id="recycleId">
      <view slot="before">长列表前面的内容</view>
      <recycle-item wx:for="{{recycleList}}" wx:key="id">
        <view>
            <image style='width:80px;height:80px;float:left;' src="{{item.image_url}}"></image>
          {{item.idx+1}}. {{item.title}}
        </view>
      </recycle-item>
      <view slot="after">长列表后面的内容</view>
    </recycle-view>

    recycle-view 的属性介绍如下:

    字段名 类型 必填 描述
    id String id必须是页面唯一的字符串
    batch Boolean 必须设置为{{batchSetRecycleData}}才能生效
    height Number 设置recycle-view的高度,默认为页面高度
    width Number 设置recycle-view的宽度,默认是页面的宽度
    enable-back-to-top Boolean 默认为false,同scroll-view同名字段
    scroll-top Number 默认为false,同scroll-view同名字段
    scroll-y Number 默认为true,同scroll-view同名字段
    scroll-to-index Number 设置滚动到长列表的项
    placeholder-image String 默认占位背景图片,在渲染不及时的时候显示,不建议使用大图作为占位。建议传入SVG的Base64格式,可使用工具将SVG代码转为Base64格式。支持SVG中设置rpx。
    scroll-with-animation Boolean 默认为false,同scroll-view的同名字段
    lower-threshold Number 默认为false,同scroll-view同名字段
    upper-threshold Number 默认为false,同scroll-view同名字段
    bindscroll 事件 同scroll-view同名字段
    bindscrolltolower 事件 同scroll-view同名字段
    bindscrolltoupper 事件 同scroll-view同名字段

    recycle-view 包含3个 slot,具体介绍如下:

    名称 描述
    before 默认 slot 的前面的非回收区域
    默认 slot 长列表的列表展示区域,recycle-item 必须定义在默认 slot 中
    after 默认 slot 的后面的非回收区域

    ​ 长列表的内容实际是在一个 scroll-view 滚动区域里面的,当长列表里面的内容,不止是单独的一个列表的时候,例如我们页面底部都会有一个 copyright 的声明,我们就可以把这部分的内容放在 before 和 after 这2个 slot 里面。

    recycle-item 的介绍如下:

    ​ 需要注意的是,recycle-item 中必须定义 wx:for 列表循环,不应该通过 setData 来设置 wx:for 绑定的变量,而是通过createRecycleContext方法创建RecycleContext对象来管理数据,createRecycleContext在 index.js 文件里面定义。建议同时设置 wx:key,以提升列表的渲染性能。

  3. 页面 JS 管理 recycle-view 的数据

    const createRecycleContext = require('miniprogram-recycle-view')
    Page({
        onReady: function() {
            var ctx = createRecycleContext({
              id: 'recycleId',
              dataKey: 'recycleList',
              page: this,
              itemSize: { // 这个参数也可以直接传下面定义的this.itemSizeFunc函数
                width: 162,
                height: 182
              }
            })
            ctx.append(newList)
            // ctx.update(beginIndex, list)
            // ctx.destroy()
        },
        itemSizeFunc: function (item, idx) {
            return {
                width: 162,
                height: 182
            }
        }
    })

    ​ 页面必须通过 Component 构造器定义,页面引入了miniprogram-recycle-view包之后,会在 wx 对象下面新增接口createRecycleContext函数创建RecycleContext对象来管理 recycle-view 定义的的数据,createRecycleContext接收类型为1个 Object 的参数,Object 参数的每一个 key 的介绍如下:

    参数名 类型 描述
    id String 对应 recycle-view 的 id 属性的值
    dataKey String 对应 recycle-item 的 wx:for 属性设置的绑定变量名
    page Page/Component recycle-view 所在的页面或者组件的实例,页面或者组件内可以直接传 this
    itemSize Object/Function 此参数用来生成recycle-item的宽和高,前面提到过,要知道当前需要渲染哪些item,必须知道item的宽高才能进行计算
    Object必须包含{width, height}两个属性,Function的话接收item, index这2个参数,返回一个包含{width, height}的Object
    itemSize如果是函数,函数里面this指向RecycleContext
    如果样式使用了rpx,可以通过transformRpx来转化为px。
    为Object类型的时候,还有另外一种用法,详细情况见下面的itemSize章节的介绍。
    useInPage Boolean 是否整个页面只有recycle-view。Page的定义里面必须至少加空的onPageScroll函数,主要是用在页面级别的长列表,并且需要用到onPullDownRefresh的效果。切必须设置root参数为当前页面对象
    root Page 当前页面对象,可以通过getCurrentPages获取, 当useInPage为true必须提供

    RecycleContext 对象提供的方法有:

    方法 参数 说明
    append list, callback 在当前的长列表数据上追加list数据,callback是渲染完成的回调函数
    splice begin, count, list, callback 插入/删除长列表数据,参数同Array的splice函数,callback是渲染完成的回调函数
    update begin, list, callback 更新长列表的数据,从索引参数begin开始,更新为参数list,参数callback同splice。
    destroy 销毁RecycleContext对象,在recycle-view销毁的时候调用此方法
    forceUpdate callback, reinitSlot 重新渲染recycle-view。callback是渲染完成的回调函数,当before和after这2个slot的高度发生变化时候调用此函数,reinitSlot设置为true。当item的宽高发生变化的时候也需要调用此方法。
    getBoundingClientRect index 获取某个数据项的在长列表中的位置,返回{left, top, width, height}的Object。
    getScrollTop 获取长列表的当前的滚动位置。
    transformRpx rpx 将rpx转化为px,返回转化后的px整数。itemSize返回的宽高单位是px,可以在这里调用此函数将rpx转化为px,参数是Number,例如ctx.transformRpx(140),返回70。注意,transformRpx会进行四舍五入,所以transformRpx(20) + transformRpx(90)不一定等于transformRpx(110)
    getViewportItems inViewportPx 获取在视窗内的数据项,用于判断某个项是否出现在视窗内。用于曝光数据上报,菜品和类别的联动效果实现。参数inViewportPx表示距离屏幕多少像素为出现在屏幕内,可以为负值。
    getList 获取到完整的数据列表

    itemSize使用

    itemSize可以为包含{width, height}的Object,所有数据只有一种宽高信息。如果有多种,则可以提供一个函数,长列表组件会调用这个函数生成每条数据的宽高信息,如下所示:

    function(item, index) {
        return {
            width: 195,
            height: item.azFirst ? 130 : 120
        }
    }

    Tips

    1. recycle-view设置batch属性的值必须为{{batchSetRecycleData}}。
    2. recycle-item的宽高必须和itemSize设置的宽高一致,否则会出现跳动的bug。
    3. recycle-view设置的高度必须和其style里面设置的样式一致。
    4. createRecycleContext(options)的id参数必须和recycle-view的id属性一致,dataKey参数必须和recycle-item的wx:for绑定的变量名一致。
    5. 不能在recycle-item里面使用wx:for的index变量作为索引值的,请使用{{item.__index__}}替代。
    6. 不要通过setData设置recycle-item的wx:for的变量值,建议recycle-item设置wx:key属性。
    7. 如果长列表里面包含图片,必须保证图片资源是有HTTP缓存的,否则在滚动过程中会发起很多的图片请求。
    8. 有些数据不一定会渲染出来,使用wx.createSelectorQuery的时候有可能会失效,可使用RecycleContext的getBoundingClientRect来替代。
    9. 当使用了useInPage参数的时候,必须在Page里面定义onPageScroll事件。
  4. transformRpx会进行四舍五入,所以transformRpx(20) + transformRpx(90)不一定等于transformRpx(110)

  5. 如果一个页面有多个长列表,必须多设置batch-key属性,每个的batch-key的值和batch属性的变量必须不一致。例如

<recycle-view batch="{{batchSetRecycleData}}" batch-key="batchSetRecycleData"></recycle-view>
<recycle-view batch="{{batchSetRecycleData1}}" batch-key="batchSetRecycleData1"></recycle-view>
You can’t perform that action at this time.