codecamp

微信小程序 扩展组件·小程序长列表组件

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

2.在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置

{
  "usingComponents": {
    "recycle-view": "miniprogram-recycle-view/recycle-view",
    "recycle-item": "miniprogram-recycle-view/recycle-item"
  }
}

3.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 的属性介绍如下:

字段名类型必填描述
idStringid必须是页面唯一的字符串
batchBoolean必须设置为{{batchSetRecycleData}}才能生效
heightNumber设置recycle-view的高度,默认为页面高度
widthNumber设置recycle-view的宽度,默认是页面的宽度
enable-back-to-topBoolean默认为false,同scroll-view同名字段
scroll-topNumber默认为false,同scroll-view同名字段
scroll-yNumber默认为true,同scroll-view同名字段
scroll-to-indexNumber设置滚动到长列表的项
placeholder-imageString默认占位背景图片,在渲染不及时的时候显示,不建议使用大图作为占位。建议传入SVG的Base64格式,可使用工具将SVG代码转为Base64格式。支持SVG中设置rpx。
scroll-with-animationBoolean默认为false,同scroll-view的同名字段
lower-thresholdNumber默认为false,同scroll-view同名字段
upper-thresholdNumber默认为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,以提升列表的渲染性能。

4.页面 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 的介绍如下:

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

RecycleContext 对象提供的方法有:

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

其中 itemSize 的使用

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

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

提示:

  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事件。
  10. transformRpx会进行四舍五入,所以transformRpx(20) + transformRpx(90)不一定等于transformRpx(110)
  11. 如果一个页面有多个长列表,必须多设置batch-key属性,每个的batch-key的值和batch属性的变量必须不一致。例如
<recycle-view batch="{{batchSetRecycleData}}" batch-key="batchSetRecycleData"></recycle-view>
<recycle-view batch="{{batchSetRecycleData1}}" batch-key="batchSetRecycleData1"></recycle-view>


微信小程序 扩展组件·视频滑动切换组件
微信小程序 扩展组件·粘性布局组件
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

微信小程序 指南

目录结构

开放能力

微信小程序 调试

微信小程序 实时日志

微信小程序 小程序测速

微信小程序 基础组件

微信小程序 API

媒体

界面

微信小程序API 绘图

微信小程序 服务端

接口调用凭证

统一服务消息

微信小程序 服务市场

微信小程序 生物认证

微信小程序 云开发

服务端

微信小程序云开发服务端API 数据库

SDK文档

微信小程序 扩展能力

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }