小程序自定义组件
使用此组件需要依赖小程序基础库 2.2.2 版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档。
电商小程序往往需要展示很多商品,当一个页面展示很多的商品信息的时候,会造成小程序页面的卡顿以及白屏。原因有如下几点:
- 商品列表数据很大,首次 setData 的时候耗时高
- 渲染出来的商品列表 DOM 结构多,每次 setData 都需要创建新的虚拟树、和旧树 diff 操作耗时都比较高
- 渲染出来的商品列表 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 |
- 安装组件
npm install --save miniprogram-recycle-view
-
在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置
{ "usingComponents": { "recycle-view": "miniprogram-recycle-view/recycle-view", "recycle-item": "miniprogram-recycle-view/recycle-item" } }
-
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-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 的后面的非回收区域 itemsize 动态生成宽高的预先加载数据的slot 长列表的内容实际是在一个 scroll-view 滚动区域里面的,当长列表里面的内容,不止是单独的一个列表的时候,例如我们页面底部都会有一个 copyright 的声明,我们就可以把这部分的内容放在 before 和 after 这2个 slot 里面。
recycle-item 的介绍如下:
需要注意的是,recycle-item 中必须定义 wx:for 列表循环,不应该通过 setData 来设置 wx:for 绑定的变量,而是通过
createRecycleContext
方法创建RecycleContext
对象来管理数据,createRecycleContext
在 index.js 文件里面定义。建议同时设置 wx:key,以提升列表的渲染性能。 -
页面 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的效果。 placeholderClass Array 和itemSize的最后一种用法一起配合,自动生成placeholder-image占位背景图。详见最后itemSize章节的介绍。 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。 getViewportItems inViewportPx 获取在视窗内的数据项,用于判断某个项是否出现在视窗内。用于曝光数据上报,菜品和类别的联动效果实现。参数inViewportPx表示距离屏幕多少像素为出现在屏幕内,可以为负值。 itemSize主要是用于生成每条数据的宽高信息,有下面的2种宽高信息生成的方法。
基本的思路是提供少量的数据进行预先渲染,然后通过createSelectorQuery接口查询得到高度信息。
JS的调用方法如下:
var ctx = createRecycleContext({ id: 'recycleId', dataKey: 'recycleList', page: this, itemSize: { props: ['azFirst'], cacheKey: 'cacheKey', // 预先缓存的key queryClass: 'recycle-itemsize', // 动态查询的class dataKey: 'recycleListItemSize', // 预先渲染的数据的wx:for绑定的变量 }, placeholderClass: ['recycle-image', 'recycle-text'] })
itemSize的各个属性,要配合wxml的详细写法来生成,先看下wxml的写法:
<recycle-view wx:if="{{showRecycleview}}" class="recycle-list" placeholder-image="{{placeholderImage}}" bindscrolltolower="scrollToLower" scroll-with-animation="{{true}}" scroll-to-index="{{index}}" scroll-top="{{scrollTop}}" batch="{{batchSetRecycleData}}" height="500" id="recycleId"> <view slot="itemsize"> <template is="recycleItem" data="{{item: item, recycleList: recycleListItemSize}}"></template> </view> <template is="recycleItem" data="{{item: item, recycleList: recycleList}}"></template> <view slot="after" style="height:200px;">after height:200px view</view> </recycle-view> <template name="recycleItem"> <recycle-item style="width:50%;" wx:for="{{recycleList}}" wx:key="id"> <view class="recycle-itemsize" style="width:100%;height:{{item.test.azFirst ? 160 : 160}}px;position: relative;"> <image class='recycle-image' style='width:80px;height:80px;' src="{{item.image_url}}?imageView2/2/w/120/h/0/q/120/format/jpg"></image> <text class="recycle-text">{{item.idx+1}}. {{item.title}}</text> </view> </recycle-item> </template>
JS和wxml里面的class的对应关系如下图所示:
还有2个属性props和cacheKey,其中props可以指定数据的多个key,长列表组件会自动聚合key对应的值,并取出数据进行预先渲染并查询得到高度。假设数据为
[{ azFirst: true, // 对应高度130px ... }, { azFirst: false, // 对应高度120px ... }]
props指定的
azFirst
对应的数据里面的属性。由于自动计算高度有一定耗时,可以指定cacheKey,长列表则会将计算的高度缓存起来,下次使用。当数据的宽高发生改变的时候,必须要重新指定一个cacheKey。
当指定了
placeholderClass
之后,则会自动生成占位背景图设置在recycle-view
组件的placeholder-image
属性上。itemSize可以为包含{width, height}的Object,所有数据只有一种宽高信息。如果有多种,则可以提供一个函数,长列表组件会调用这个函数生成每条数据的宽高信息,如下所示:
function(item, index) { return { width: 195, height: item.azFirst ? 130 : 120 } }
- recycle-view设置batch属性的值必须为{{batchSetRecycleData}}。
- recycle-item的宽高必须和itemSize设置的宽高一致,否则会出现跳动的bug。
- recycle-view设置的高度必须和其style里面设置的样式一致。
createRecycleContext(options)
的id参数必须和recycle-view的id属性一致,dataKey参数必须和recycle-item的wx:for绑定的变量名一致。- 不能在recycle-item里面使用wx:for的index变量作为索引值的,请使用{{item.__index__}}替代。
- 不要通过setData设置recycle-item的wx:for的变量值,建议recycle-item设置wx:key属性。
- 如果长列表里面包含图片,必须保证图片资源是有HTTP缓存的,否则在滚动过程中会发起很多的图片请求。
- 有些数据不一定会渲染出来,使用wx.createSelectorQuery的时候有可能会失效,可使用RecycleContext的getBoundingClientRect来替代。
- 当使用了useInPage参数的时候,必须在Page里面定义onPageScroll事件。