了解如何正確使用list,優(yōu)化列表渲染性能,靈活實(shí)現(xiàn)需求
通過(guò)本節(jié),你將學(xué)會(huì):
開發(fā)者在頁(yè)面中實(shí)現(xiàn)長(zhǎng)列表
或者屏幕滾動(dòng)
等效果時(shí),習(xí)慣使用div組件
做循環(huán)遍歷
示例如下:
假設(shè)開發(fā)者要這樣的效果:一個(gè)結(jié)構(gòu)簡(jiǎn)單的商品列表
使用 div 組件的代碼如下:
<template>
<!-- div實(shí)現(xiàn) -->
<div class="tutorial-page">
<!-- 商品列表 -->
<block for="productList">
<div 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>
</div>
</block>
<!-- 加載更多,監(jiān)聽通用事件appear,出現(xiàn)時(shí)加載更多數(shù)據(jù) -->
<div class="load-more" onappear="loadMoreData">
<progress type="circular"></progress>
<text>加載更多</text>
</div>
</div>
</template>
然而,當(dāng) DOM 結(jié)構(gòu)復(fù)雜時(shí),滾動(dòng)頁(yè)面會(huì)出現(xiàn)卡頓現(xiàn)象,因?yàn)?Native 無(wú)法復(fù)用div組件實(shí)現(xiàn)的列表元素
為了得到流暢的列表滾動(dòng)體驗(yàn),推薦開發(fā)者使用list組件
替代div組件
實(shí)現(xiàn)長(zhǎng)列表布局,因?yàn)?Native 會(huì)復(fù)用相同type屬性
的list-item
使用 list 組件的代碼如下:
<template>
<!-- 列表實(shí)現(xiàn) -->
<list class="tutorial-page" onscrollbottom="loadMoreData">
<!-- 商品列表 -->
<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>
<!-- 加載更多,type屬性自定義命名為loadMore -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加載更多</text>
</list-item>
</list>
</template>
要實(shí)現(xiàn) DOM 片段的復(fù)用,要求相同type屬性
的 DOM 結(jié)構(gòu)完全相同;所以設(shè)置相同type屬性
的list-item
是優(yōu)化列表滾動(dòng)性能的關(guān)鍵
注意:
list-item
內(nèi)不能再嵌套list
list-item
的type屬性
為必填屬性list-item
內(nèi)部需謹(jǐn)慎使用if指令
或for指令
,因?yàn)橄嗤?code>type屬性的list-item
的 DOM 結(jié)構(gòu)必須完全相同,而使用if指令
或for指令
會(huì)造成 DOM 結(jié)構(gòu)差異提示:
若遇到類似xxx cannot be cast to xxx at ...list的錯(cuò)誤,請(qǐng)檢查list-item組件
是否存在如下情形:
type屬性
。解決方案:設(shè)置type屬性
if指令
。解決方案:使用show指令
代替if指令
,或設(shè)置不同的type屬性
type屬性
,但 DOM 結(jié)構(gòu)不一致。解決方案:設(shè)置不同的type屬性
實(shí)現(xiàn)簡(jiǎn)單的商品列表,了解list組件
的基本用法和優(yōu)化性能的關(guān)鍵后,接下來(lái)通過(guò)實(shí)現(xiàn)多種列表元素類型的復(fù)雜列表,進(jìn)一步了解list組件
示例如下:
假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:一個(gè)商品列表頁(yè),圖片位于左邊和圖片位于右邊的商品交錯(cuò)顯示
列表中的列表元素可以分為三類,設(shè)置三種不同type屬性
的list-item
。分別為:
list-item
,type屬性
自定義命名為productLeft
list-item
,type屬性
自定義命名為productRight
list-item
,type屬性
自定義命名為loadMore
示例代碼如下:
<template>
<!-- list中可以劃分為三種類型的DOM結(jié)構(gòu),對(duì)應(yīng)三種type屬性的list-item -->
<list class="tutorial-page" onscrollbottom="loadMoreData">
<block for="{{productList}}">
<!-- 圖片在左,文字在右的list-item,type屬性自定義命名為productLeft -->
<list-item type="productLeft" class="content-item" if="{{$idx%2 === 0}}" 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>
<!-- 圖片在右,文字在左的list-item,type屬性自定義命名為productRight -->
<list-item type="productRight" class="content-item" if="{{$idx%2 === 1}}" onclick="route($item.url)">
<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>
<image class="img" src="{{$item.img}}"></image>
</list-item>
</block>
<!-- 加載更多的list-item,type屬性自定義命名為loadMore -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加載更多</text>
</list-item>
</list>
</template>
當(dāng) DOM 結(jié)構(gòu)復(fù)雜時(shí),為了得到流暢的列表滾動(dòng)體驗(yàn),list組件
的性能優(yōu)化必不可缺
list組件
的性能優(yōu)化分為精簡(jiǎn)DOM層級(jí)
、復(fù)用list-item
、細(xì)粒度劃分list-item
、關(guān)閉scrollpage
四個(gè)方面
其中,精簡(jiǎn)DOM層級(jí)
、復(fù)用list-item
是使用list組件
必須遵循的優(yōu)化原則,細(xì)粒度劃分list-item
、關(guān)閉scrollpage
適用于部分場(chǎng)景,詳見下文
精簡(jiǎn)DOM層級(jí),即減少DOM樹的級(jí)數(shù)和分支上的DOM節(jié)點(diǎn)數(shù)。層級(jí)越少、數(shù)量越少,布局和繪制就會(huì)越快
因此,開發(fā)者需要盡量剔除list中無(wú)意義的包裹類標(biāo)簽和層級(jí)
復(fù)用list-item
,即列表中相同的DOM結(jié)構(gòu)設(shè)置為同一type屬性
的list-item
,這是優(yōu)化列表滾動(dòng)體驗(yàn)的關(guān)鍵
細(xì)粒度劃分list-item
,即列表中相同的DOM結(jié)構(gòu)劃分為盡可能小的列表元素(即list-item
)
示例如下:
假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:商品按類別分類,展示多種類別
從業(yè)務(wù)角度,可按類別劃分為不同type屬性
的list-item
然而,當(dāng)list-item
復(fù)雜時(shí),會(huì)出現(xiàn)卡頓現(xiàn)象。推薦拋開業(yè)務(wù)邏輯,劃分為盡可能小的列表元素
示例代碼如下:
<template>
<list class="tutorial-page" onscrollbottom="loadMoreData">
<!-- 細(xì)粒度劃分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" if="$item.productMini" class="product-mini-wrap">
<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>
list組件
支持屬性scrollpage
,默認(rèn)關(guān)閉,標(biāo)志是否將頂部頁(yè)面中非list
的元素隨list
一起滾動(dòng)。開啟scrollpage
會(huì)降低list
渲染性能
因此,在開發(fā)者開啟scrollpage
前,推薦先嘗試將頂部頁(yè)面中非list
的元素,作為一種或多種type屬性
的list-item
,移入list
中,從而達(dá)到關(guān)閉scrollpage
提高渲染性能的目的
示例如下:
假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:頂部banner,banner下方為常見列表,需要整屏滾動(dòng)
開發(fā)者一般會(huì)將頁(yè)面劃分為banner和list兩部分,然后開啟list
的scrollpage
屬性,實(shí)現(xiàn)整屏滾動(dòng)
然而,開啟scrollpage
會(huì)降低list
渲染性能,推薦將頂部banner作為一種特殊type屬性
的list-item
,移入list
中,關(guān)閉scrollpage
示例代碼如下:
<template>
<!-- 列表實(shí)現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動(dòng)到底部時(shí)加載更多數(shù)據(jù) -->
<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實(shí)現(xiàn)的加載更多,type屬性自定義命名為loadMore -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加載更多</text>
</list-item>
</list>
</template>
懶加載,簡(jiǎn)稱lazyload
,本質(zhì)上是按需加載
在傳統(tǒng)的頁(yè)面中,常用lazyload
優(yōu)化網(wǎng)頁(yè)的性能:
可視區(qū)域
時(shí),再加載資源在框架中,開發(fā)者也可使用lazyload
概念優(yōu)化列表的渲染:
memList
中,當(dāng)list
滾動(dòng)到底部時(shí),從memList
中提取部分?jǐn)?shù)據(jù)來(lái)渲染list-item
。當(dāng)memList
中數(shù)據(jù)不足時(shí),提前fetch請(qǐng)求數(shù)據(jù),填充memList
list-item
的等待時(shí)間示例如下:
假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:一個(gè)商品列表,每次渲染10個(gè)商品
memList
中,從memList
中提取部分?jǐn)?shù)據(jù)渲染列表memList
中是否有足夠數(shù)據(jù),有則直接從memList
中提取部分?jǐn)?shù)據(jù)渲染,而不是才開始網(wǎng)絡(luò)請(qǐng)求,減少時(shí)間消耗。當(dāng)memList
中數(shù)據(jù)不足時(shí),提前請(qǐng)求數(shù)據(jù)示例代碼如下:
<template>
<!-- 列表實(shí)現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動(dòng)到底部時(shí)加載更多數(shù)據(jù) -->
<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請(qǐng)求數(shù)據(jù)
function callFetch (callback) {
setTimeout(function () {
callback(dataComponentListLazyload)
}, 500)
}
// 內(nèi)存中存儲(chǔ)的列表數(shù)據(jù)
let memList = []
export default {
data: {
productList: [],
hasMoreData: true,
// 每次渲染的商品數(shù)
size: 10,
// 是否正在fetch請(qǐng)求數(shù)據(jù)
isLoadingData: false
},
onInit () {
this.$page.setTitleBar({ text: 'list-item懶加載' })
// 獲取數(shù)據(jù)并渲染列表
this.loadAndRender()
},
/**
* 請(qǐng)求并渲染
*/
loadAndRender (doRender = true) {
this.isLoadingData = true
// 重新請(qǐng)求數(shù)據(jù)并根據(jù)模式判斷是否需要渲染列表
callFetch(function (resList) {
this.isLoadingData = false
if (!resList) {
console.error(`數(shù)據(jù)請(qǐng)求錯(cuò)誤`)
}
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) {
// 提前請(qǐng)求新的數(shù)據(jù)
this.loadAndRender(false)
}
},
/**
* 滑動(dòng)到底部時(shí)加載更多
*/
renderMoreListItem () {
if (!this.isLoadingData) {
this._renderList()
}
}
}
</script>
注意:避免在ViewModel
的data
屬性中定義memList
。因?yàn)樵?code>ViewModel的data
屬性中定義變量會(huì)觸發(fā)set/get數(shù)據(jù)驅(qū)動(dòng)定義
,而memList
作為暫時(shí)保存數(shù)據(jù)的變量,不需監(jiān)聽數(shù)據(jù)變化
本部分非必讀,旨在為有以下需求之一的開發(fā)者提供參考:
appear事件
和disappear事件
吸頂
是傳統(tǒng)web頁(yè)面中的一種比較老的交互方式:
吸頂元素
的初始位置一般靠近頁(yè)面頂部,但與頂部有一定的距離吸頂元素
的初始位置時(shí),把吸頂元素
固定在頂部吸頂元素
的初始位置時(shí),取消吸頂元素
在頂部的固定吸頂
在傳統(tǒng)web頁(yè)面中的實(shí)現(xiàn)思路是監(jiān)聽scroll事件
,當(dāng)頁(yè)面滾動(dòng)到一定位置時(shí),做一些事情來(lái)改變吸頂元素
在窗口中的位置
然而,與傳統(tǒng)web頁(yè)面不同,在框架中,scroll事件
僅適用于list組件
,且獲取的值是滾動(dòng)的相對(duì)坐標(biāo)值,在使用時(shí),需要通過(guò)累加來(lái)獲取當(dāng)前滾動(dòng)位置的絕對(duì)坐標(biāo)
此外,scroll事件
在列表滾動(dòng)時(shí)會(huì)被高頻觸發(fā),存在潛在性能問題
因此,在框架中,推薦開發(fā)者使用appear事件
和disappear事件
來(lái)實(shí)現(xiàn)吸頂
效果,appear事件
在組件出現(xiàn)時(shí)觸發(fā),disappear事件
在組件消失時(shí)觸發(fā)
appear事件
和disappear事件
是組件的通用事件,文檔中標(biāo)有支持通用事件的組件都支持這兩個(gè)事件,包括div組件
、list-item組件
等
靈活使用appear事件
和disappear事件
,能實(shí)現(xiàn)大部分需要判斷滾動(dòng)位置的需求
接下來(lái),對(duì)應(yīng)在list組件
中實(shí)現(xiàn)吸頂
效果的示例代碼,具體分析實(shí)現(xiàn)思路
首先,了解頂部元素
和吸頂元素
:
頂部元素
:type屬性
為top
的list-item
吸頂元素
:type屬性
為ceiling
的list-item
然后,分析吸頂
效果實(shí)現(xiàn)方案:
stack組件
做為整個(gè)頁(yè)面的容器,stack組件
的特性為:每個(gè)直接子組件按照先后順序依次堆疊,覆蓋前一個(gè)子組件stack組件
中增加一個(gè)排在最后的子組件,作為mask
遮擋之前的子組件,顯示效果為一直固定在頂部,這個(gè)mask
與吸頂元素
渲染效果完全一致吸頂元素
需要吸頂
時(shí),顯示對(duì)應(yīng)的mask
,實(shí)現(xiàn)吸頂?shù)男Ч?;?dāng)吸頂元素
不需要吸頂
時(shí),隱藏對(duì)應(yīng)的mask
最后,判斷吸頂
條件:
頂部元素
消失在視野時(shí),吸頂元素
需要固定在頂部,因此,監(jiān)聽頂部元素
的disappear事件
,顯示mask
頂部元素
出現(xiàn)在視野時(shí),吸頂元素
需要取消固定,因此,監(jiān)聽頂部元素
的appear事件
,隱藏mask
示例代碼如下:
<template>
<!-- 利用stack組件,使"列表中的吸頂元素對(duì)應(yīng)的Mask"覆蓋列表 -->
<stack class="tutorial-page">
<list class="list">
<!-- 通過(guò)監(jiān)聽"列表中的頂部元素"的元素的appear和disappear事件,控制"列表中的吸頂元素對(duì)應(yīng)的Mask"的顯示 -->
<list-item type="top" ondisappear="showMask" onappear="hideMask">
<div class="height-300 bg-blue">
<text>列表中的頂部元素</text>
</div>
</list-item>
<!-- 列表中的吸頂元素 -->
<list-item type="ceiling">
<div class="height-300 bg-red">
<text>列表中的吸頂元素</text>
</div>
</list-item>
<!-- 普通列表元素 -->
<list-item for="list" type="common" class="list-item">
<text class="text">{{$item}}</text>
</list-item>
</list>
<!-- 列表中的吸頂元素對(duì)應(yīng)的Mask -->
<div show="{{maskShow}}">
<div class="height-300 bg-red">
<text>列表中的吸頂元素</text>
</div>
</div>
</stack>
</template>
<style lang="less">
.tutorial-page {
flex-direction: column;
.list {
width: 750px;
flex-grow: 1;
.list-item {
height: 150px;
border-bottom-width: 1px;
border-bottom-color: #0faeff;
.text {
flex: 1;
text-align: center;
}
}
}
.height-300 {
height: 300px;
}
.bg-red {
flex-grow: 1;
justify-content: center;
background-color: #f76160;
}
.bg-blue {
flex-grow: 1;
justify-content: center;
background-color: #0faeff;
}
}
</style>
<script>
export default {
data: {
maskShow: false,
appearCount: 0,
list: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']
},
onInit(){
this.$page.setTitleBar({ text: '效果展示:吸頂' })
},
showMask () {
this.maskShow = true
},
hideMask () {
// 加載頁(yè)面時(shí),所有元素的appear事件都會(huì)被觸發(fā)一次。因此,需要過(guò)濾第一次的appear事件
if (this.appearCount) {
this.maskShow = false
} else {
++this.appearCount
}
}
}
</script>
了解list組件的特點(diǎn),可以更好的提升頁(yè)面性能,避免后期開發(fā)過(guò)程中引起的性能問題
更多建議: