W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
了解如何正確使用list,優(yōu)化列表渲染性能,靈活實現(xiàn)需求
通過本節(jié),你將學會:
開發(fā)者在頁面中實現(xiàn)長列表
或者屏幕滾動
等效果時,習慣使用div組件
做循環(huán)遍歷
示例如下:
假設開發(fā)者要這樣的效果:一個結(jié)構(gòu)簡單的商品列表
使用 div 組件的代碼如下:
<template>
<!-- div實現(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ù)據(jù) -->
<div class="load-more" onappear="loadMoreData">
<progress type="circular"></progress>
<text>加載更多</text>
</div>
</div>
</template>
然而,當 DOM 結(jié)構(gòu)復雜時,滾動頁面會出現(xiàn)卡頓現(xiàn)象,因為 Native 無法復用div組件實現(xiàn)的列表元素
為了得到流暢的列表滾動體驗,推薦開發(fā)者使用list組件
替代div組件
實現(xiàn)長列表布局,因為 Native 會復用相同type屬性
的list-item
使用 list 組件的代碼如下:
<template>
<!-- 列表實現(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>
要實現(xiàn) DOM 片段的復用,要求相同type屬性
的 DOM 結(jié)構(gòu)完全相同;所以設置相同type屬性
的list-item
是優(yōu)化列表滾動性能的關鍵
注意:
list-item
內(nèi)不能再嵌套list
list-item
的type屬性
為必填屬性list-item
內(nèi)部需謹慎使用if指令
或for指令
,因為相同type屬性
的list-item
的 DOM 結(jié)構(gòu)必須完全相同,而使用if指令
或for指令
會造成 DOM 結(jié)構(gòu)差異提示:
若遇到類似xxx cannot be cast to xxx at ...list的錯誤,請檢查list-item組件
是否存在如下情形:
type屬性
。解決方案:設置type屬性
if指令
。解決方案:使用show指令
代替if指令
,或設置不同的type屬性
type屬性
,但 DOM 結(jié)構(gòu)不一致。解決方案:設置不同的type屬性
實現(xiàn)簡單的商品列表,了解list組件
的基本用法和優(yōu)化性能的關鍵后,接下來通過實現(xiàn)多種列表元素類型的復雜列表,進一步了解list組件
示例如下:
假設開發(fā)者要實現(xiàn)這樣的效果:一個商品列表頁,圖片位于左邊和圖片位于右邊的商品交錯顯示
列表中的列表元素可以分為三類,設置三種不同type屬性
的list-item
。分別為:
list-item
,type屬性
自定義命名為productLeft
list-item
,type屬性
自定義命名為productRight
list-item
,type屬性
自定義命名為loadMore
示例代碼如下:
<template>
<!-- list中可以劃分為三種類型的DOM結(jié)構(gòu),對應三種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>
當 DOM 結(jié)構(gòu)復雜時,為了得到流暢的列表滾動體驗,list組件
的性能優(yōu)化必不可缺
list組件
的性能優(yōu)化分為精簡DOM層級
、復用list-item
、細粒度劃分list-item
、關閉scrollpage
四個方面
其中,精簡DOM層級
、復用list-item
是使用list組件
必須遵循的優(yōu)化原則,細粒度劃分list-item
、關閉scrollpage
適用于部分場景,詳見下文
精簡DOM層級,即減少DOM樹的級數(shù)和分支上的DOM節(jié)點數(shù)。層級越少、數(shù)量越少,布局和繪制就會越快
因此,開發(fā)者需要盡量剔除list中無意義的包裹類標簽和層級
復用list-item
,即列表中相同的DOM結(jié)構(gòu)設置為同一type屬性
的list-item
,這是優(yōu)化列表滾動體驗的關鍵
細粒度劃分list-item
,即列表中相同的DOM結(jié)構(gòu)劃分為盡可能小的列表元素(即list-item
)
示例如下:
假設開發(fā)者要實現(xiàn)這樣的效果:商品按類別分類,展示多種類別
從業(yè)務角度,可按類別劃分為不同type屬性
的list-item
然而,當list-item
復雜時,會出現(xiàn)卡頓現(xiàn)象。推薦拋開業(yè)務邏輯,劃分為盡可能小的列表元素
示例代碼如下:
<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" 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
,默認關閉,標志是否將頂部頁面中非list
的元素隨list
一起滾動。開啟scrollpage
會降低list
渲染性能
因此,在開發(fā)者開啟scrollpage
前,推薦先嘗試將頂部頁面中非list
的元素,作為一種或多種type屬性
的list-item
,移入list
中,從而達到關閉scrollpage
提高渲染性能的目的
示例如下:
假設開發(fā)者要實現(xiàn)這樣的效果:頂部banner,banner下方為常見列表,需要整屏滾動
開發(fā)者一般會將頁面劃分為banner和list兩部分,然后開啟list
的scrollpage
屬性,實現(xiàn)整屏滾動
然而,開啟scrollpage
會降低list
渲染性能,推薦將頂部banner作為一種特殊type屬性
的list-item
,移入list
中,關閉scrollpage
示例代碼如下:
<template>
<!-- 列表實現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動到底部時加載更多數(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實現(xiàn)的加載更多,type屬性自定義命名為loadMore -->
<list-item type="loadMore" class="load-more">
<progress type="circular"></progress>
<text>加載更多</text>
</list-item>
</list>
</template>
懶加載,簡稱lazyload
,本質(zhì)上是按需加載
在傳統(tǒng)的頁面中,常用lazyload
優(yōu)化網(wǎng)頁的性能:
可視區(qū)域
時,再加載資源在框架中,開發(fā)者也可使用lazyload
概念優(yōu)化列表的渲染:
memList
中,當list
滾動到底部時,從memList
中提取部分數(shù)據(jù)來渲染list-item
。當memList
中數(shù)據(jù)不足時,提前fetch請求數(shù)據(jù),填充memList
list-item
的等待時間示例如下:
假設開發(fā)者要實現(xiàn)這樣的效果:一個商品列表,每次渲染10個商品
memList
中,從memList
中提取部分數(shù)據(jù)渲染列表memList
中是否有足夠數(shù)據(jù),有則直接從memList
中提取部分數(shù)據(jù)渲染,而不是才開始網(wǎng)絡請求,減少時間消耗。當memList
中數(shù)據(jù)不足時,提前請求數(shù)據(jù)示例代碼如下:
<template>
<!-- 列表實現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動到底部時加載更多數(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請求數(shù)據(jù)
function callFetch (callback) {
setTimeout(function () {
callback(dataComponentListLazyload)
}, 500)
}
// 內(nèi)存中存儲的列表數(shù)據(jù)
let memList = []
export default {
data: {
productList: [],
hasMoreData: true,
// 每次渲染的商品數(shù)
size: 10,
// 是否正在fetch請求數(shù)據(jù)
isLoadingData: false
},
onInit () {
this.$page.setTitleBar({ text: 'list-item懶加載' })
// 獲取數(shù)據(jù)并渲染列表
this.loadAndRender()
},
/**
* 請求并渲染
*/
loadAndRender (doRender = true) {
this.isLoadingData = true
// 重新請求數(shù)據(jù)并根據(jù)模式判斷是否需要渲染列表
callFetch(function (resList) {
this.isLoadingData = false
if (!resList) {
console.error(`數(shù)據(jù)請求錯誤`)
}
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) {
// 提前請求新的數(shù)據(jù)
this.loadAndRender(false)
}
},
/**
* 滑動到底部時加載更多
*/
renderMoreListItem () {
if (!this.isLoadingData) {
this._renderList()
}
}
}
</script>
注意:避免在ViewModel
的data
屬性中定義memList
。因為在ViewModel
的data
屬性中定義變量會觸發(fā)set/get數(shù)據(jù)驅(qū)動定義
,而memList
作為暫時保存數(shù)據(jù)的變量,不需監(jiān)聽數(shù)據(jù)變化
本部分非必讀,旨在為有以下需求之一的開發(fā)者提供參考:
appear事件
和disappear事件
吸頂
是傳統(tǒng)web頁面中的一種比較老的交互方式:
吸頂元素
的初始位置一般靠近頁面頂部,但與頂部有一定的距離吸頂元素
的初始位置時,把吸頂元素
固定在頂部吸頂元素
的初始位置時,取消吸頂元素
在頂部的固定吸頂
在傳統(tǒng)web頁面中的實現(xiàn)思路是監(jiān)聽scroll事件
,當頁面滾動到一定位置時,做一些事情來改變吸頂元素
在窗口中的位置
然而,與傳統(tǒng)web頁面不同,在框架中,scroll事件
僅適用于list組件
,且獲取的值是滾動的相對坐標值,在使用時,需要通過累加來獲取當前滾動位置的絕對坐標
此外,scroll事件
在列表滾動時會被高頻觸發(fā),存在潛在性能問題
因此,在框架中,推薦開發(fā)者使用appear事件
和disappear事件
來實現(xiàn)吸頂
效果,appear事件
在組件出現(xiàn)時觸發(fā),disappear事件
在組件消失時觸發(fā)
appear事件
和disappear事件
是組件的通用事件,文檔中標有支持通用事件的組件都支持這兩個事件,包括div組件
、list-item組件
等
靈活使用appear事件
和disappear事件
,能實現(xiàn)大部分需要判斷滾動位置的需求
接下來,對應在list組件
中實現(xiàn)吸頂
效果的示例代碼,具體分析實現(xiàn)思路
首先,了解頂部元素
和吸頂元素
:
頂部元素
:type屬性
為top
的list-item
吸頂元素
:type屬性
為ceiling
的list-item
然后,分析吸頂
效果實現(xiàn)方案:
stack組件
做為整個頁面的容器,stack組件
的特性為:每個直接子組件按照先后順序依次堆疊,覆蓋前一個子組件stack組件
中增加一個排在最后的子組件,作為mask
遮擋之前的子組件,顯示效果為一直固定在頂部,這個mask
與吸頂元素
渲染效果完全一致吸頂元素
需要吸頂
時,顯示對應的mask
,實現(xiàn)吸頂?shù)男Ч?;?code>吸頂元素不需要吸頂
時,隱藏對應的mask
最后,判斷吸頂
條件:
頂部元素
消失在視野時,吸頂元素
需要固定在頂部,因此,監(jiān)聽頂部元素
的disappear事件
,顯示mask
頂部元素
出現(xiàn)在視野時,吸頂元素
需要取消固定,因此,監(jiān)聽頂部元素
的appear事件
,隱藏mask
示例代碼如下:
<template>
<!-- 利用stack組件,使"列表中的吸頂元素對應的Mask"覆蓋列表 -->
<stack class="tutorial-page">
<list class="list">
<!-- 通過監(jiān)聽"列表中的頂部元素"的元素的appear和disappear事件,控制"列表中的吸頂元素對應的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>
<!-- 列表中的吸頂元素對應的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 () {
// 加載頁面時,所有元素的appear事件都會被觸發(fā)一次。因此,需要過濾第一次的appear事件
if (this.appearCount) {
this.maskShow = false
} else {
++this.appearCount
}
}
}
</script>
了解list組件的特點,可以更好的提升頁面性能,避免后期開發(fā)過程中引起的性能問題
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: