性能优化
当 DOM 结构复杂时,为了得到流畅的列表滚动体验,list组件
的性能优化必不可缺
list组件
的性能优化分为精简DOM层级
、复用list-item
、细粒度划分list-item
、关闭scrollpage
四个方面
其中,精简DOM层级
、复用list-item
是使用list组件
必须遵循的优化原则,细粒度划分list-item
、关闭scrollpage
适用于部分场景,详见下文
精简 DOM 层级
精简 DOM 层级,即减少 DOM 树的级数和分支上的 DOM 节点数。层级越少、数量越少,布局和绘制就会越快
因此,开发者需要尽量剔除 list 中无意义的包裹类标签和层级
复用 list-item
复用list-item
,即列表中相同的 DOM 结构设置为同一type属性
的list-item
,这是优化列表滚动体验的关键
细粒度划分 list-item
细粒度划分list-item
,即列表中相同的 DOM 结构划分为尽可能小的列表元素(即list-item
)
示例如下:
假设开发者要实现这样的效果:商品按类别分类,展示多种类别
从业务角度,可按类别划分为不同type属性
的list-item
然而,当list-item
复杂时,会出现卡顿现象。推荐抛开业务逻辑,划分为尽可能小的列表元素
示例代码如下:
<template>
<list class="tutorial-page" onscrollbottom="loadMoreData">
<!-- 细粒度划分list-item -->
<block for="productList">
<!-- title -->
<list-item type="title" if="$item.title" class="title {{$idx>0?'margin-top':''}}">
<text>{{$item.title}}</text>
</list-item>
<!-- banner -->
<list-item type="banner" if="$item.bannerImg" class="banner">
<image src="{{$item.bannerImg}}"></image>
</list-item>
<!-- productMini -->
<list-item type="{{'productMini'+$item.productMini.length}}" if="$item.productMini" class="product-mini-wrap">
<!-- 在当前list-item中使用了for指令,因此需要动态设置list-item的type属性。确保相同type属性的list-item的DOM结构完全一致 -->
<div for="value in $item.productMini" class="product-mini">
<image src="{{value.img}}" class="product-mini-img"></image>
<text>{{value.name}}</text>
<text class="product-mini-brief">{{value.brief}}</text>
<text class="product-mini-price">{{value.price}}</text>
</div>
</list-item>
<!-- textHint -->
<list-item type="textHint" if="$item.textHint" class="text-hint">
<text>{{$item.textHint}} ></text>
</list-item>
</block>
<!-- list底部的加载更多 -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加载更多</text>
</list-item>
</list>
</template>
关闭 scrollpage
list组件
支持属性scrollpage
,默认关闭,标志是否将顶部页面中非list
的元素随list
一起滚动。开启scrollpage
会降低list
渲染性能
因此,在开发者开启scrollpage
前,推荐先尝试将顶部页面中非list
的元素,作为一种或多种type属性
的list-item
,移入list
中,从而达到关闭scrollpage
提高渲染性能的目的
示例如下:
假设开发者要实现这样的效果:顶部 banner,banner 下方为常见列表,需要整屏滚动
开发者一般会将页面划分为 banner 和 list 两部分,然后开启list
的scrollpage
属性,实现整屏滚动
然而,开启scrollpage
会降低list
渲染性能,推荐将顶部 banner 作为一种特殊type属性
的list-item
,移入list
中,关闭scrollpage
示例代码如下:
<template>
<!-- 列表实现,监听列表的scrollbottom事件,列表滚动到底部时加载更多数据 -->
<list class="tutorial-page" onscrollbottom="loadMoreData">
<list-item type="banner" class="banner">
<image src="../../Common/img/demo_large.png"></image>
</list-item>
<!-- 商品列表 -->
<block for="productList">
<list-item type="product" class="content-item" onclick="route($item.url)">
<image class="img" src="{{$item.img}}"></image>
<div class="text-wrap">
<div class="top-line">
<text class="text-name">{{$item.name}}</text>
<text class="text-price">{{$item.price}}</text>
</div>
<text class="bottom-line">{{$item.brief}}</text>
</div>
</list-item>
</block>
<!-- list-item实现的加载更多,type属性自定义命名为loadMore -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加载更多</text>
</list-item>
</list>
</template>
list-item 懒加载
懒加载,简称lazyload
,本质上是按需加载
在传统的页面中,常用lazyload
优化网页的性能:
- 实现:不加载全部页面资源,当资源即将呈现在浏览器
可视区域
时,再加载资源 优点:加快渲染的同时避免流量浪费在框架中,开发者也可使用
lazyload
概念优化列表的渲染:实现:提前 fetch 请求足够的列表数据保存在内存变量
memList
中,当list
滚动到底部时,从memList
中提取部分数据来渲染list-item
。当memList
中数据不足时,提前 fetch 请求数据,填充memList
- 优点:每次网络请求与页面渲染的数据量不一致,减少首屏渲染占用的 JS 执行时间,减少渲染后续
list-item
的等待时间示例如下:
假设开发者要实现这样的效果:一个商品列表,每次渲染 10 个商品
- 渲染首屏时,请求数据保存在内存变量
memList
中,从memList
中提取部分数据渲染列表 - 加载更多时,首先检查
memList
中是否有足够数据,有则直接从memList
中提取部分数据渲染,而不是直接进行网络请求,减少时间消耗。当memList
中数据不足时,提前请求数据示例代码如下:
<template>
<!-- 列表实现,监听列表的scrollbottom事件,列表滚动到底部时加载更多数据 -->
<list class="tutorial-page" onscrollbottom="renderMoreListItem">
<!-- 商品列表 -->
<block for="productList">
<list-item type="product" class="content-item">
<image class="img" src="{{$item.img}}"></image>
<div class="text-wrap">
<div class="top-line">
<text class="text-name">{{$item.name}}</text>
<text class="text-price">{{$item.price}}</text>
</div>
<text class="bottom-line">{{$item.brief}}</text>
</div>
</list-item>
</block>
<list-item type="loadStatus" class="load-status">
<progress type="circular" show="{{hasMoreData}}"></progress>
<text show="{{hasMoreData}}">加载更多</text>
<text show="{{!hasMoreData}}">没有更多了~</text>
</list-item>
</list>
</template>
<script>
import {dataComponentListLazyload} from '../../Common/js/data'
// 模拟fetch请求数据
function callFetch (callback) {
setTimeout(function () {
callback(dataComponentListLazyload)
}, 500)
}
// 内存中存储的列表数据
let memList = []
export default {
private: {
productList: [],
hasMoreData: true,
// 每次渲染的商品数
size: 10,
// 是否正在fetch请求数据
isLoadingData: false
},
onInit () {
this.$page.setTitleBar({ text: 'list-item懒加载' })
// 获取数据并渲染列表
this.loadAndRender()
},
/**
* 请求并渲染
*/
loadAndRender (doRender = true) {
this.isLoadingData = true
// 重新请求数据并根据模式判断是否需要渲染列表
callFetch(function (resList) {
this.isLoadingData = false
if (!resList) {
console.error(`数据请求错误`)
}
else if (!resList.length) {
this.hasMoreData = false
}
else {
memList = memList.concat(resList)
if (doRender) {
this._renderList()
}
}
}.bind(this))
},
_renderList () {
// 渲染列表
if (memList.length > 0) {
const list = memList.splice(0, this.size)
this.productList = this.productList.concat(list)
}
if (memList.length <= this.size) {
// 提前请求新的数据
this.loadAndRender(false)
}
},
/**
* 滑动到底部时加载更多
*/
renderMoreListItem () {
if (!this.isLoadingData) {
this._renderList()
}
}
}
</script>
注意:避免在ViewModel
的数据属性中定义memList
。因为在ViewModel
的数据属性中定义变量会触发set/get数据驱动定义
,而memList
作为暂时保存数据的变量,不需监听数据变化