在典型的 Taro 應(yīng)用中,正常的列表渲染遵循以下的邏輯:
如果按照此邏輯,當(dāng)?shù)谝徊轿覀兩苫蚣虞d的數(shù)據(jù)量非常大時(shí)就可能會(huì)產(chǎn)生嚴(yán)重的性能問題,導(dǎo)致視圖無法響應(yīng)操作一段時(shí)間。為了解決這個(gè)問題,我們可以采用另一種方式:比起全量渲染數(shù)據(jù)生成的視圖,可以只渲染**當(dāng)前可視區(qū)域(visable viewport)**的視圖,非可視區(qū)域的視圖在用戶滾動(dòng)到可視區(qū)域再渲染:
使用 React/Nerv 我們可以直接從 @tarojs/components/virtual-list
引入虛擬列表(VirtualList)組件:
import VirtualList from `@tarojs/components/virtual-list`
一個(gè)最簡單的長列表組件會(huì)像這樣,VirtualList
的 5 個(gè)屬性都是必填項(xiàng):
function buildData (offset = 0) {
return Array(100).fill(0).map((_, i) => i + offset);
}
const Row = React.memo(({ index, style, data }) => {
return (
<View className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} style={style}>
Row {index}
</View>
);
})
export default class Index extends Component {
state = {
data: buildData(0),
}
render() {
const { data } = this.state
const dataLen = data.length
return (
<VirtualList
height={500} /* 列表的高度 */
width='100%' /* 列表的寬度 */
itemData={data} /* 渲染列表?的數(shù)據(jù) */
itemCount={dataLen} /* 渲染列表的長度 */
itemSize={100} /* 列表單項(xiàng)的高度 */
>
{Row} /* 列表單項(xiàng)組件,這里只能傳入一個(gè)組件 */
</VirtualList>
);
}
}
實(shí)現(xiàn)無限滾動(dòng)也非常簡單,我們只需要在列表滾動(dòng)到底部時(shí),往列表尾部追加數(shù)據(jù)即可:
const Row = React.memo(({ index, style, data }) => {
return (
<View className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} style={style}>
Row {index}
</View>
);
})
function buildData (offset = 0) {
return Array(100).fill(0).map((_, i) => i + offset);
}
export default class Index extends Component {
state = {
data: buildData(0),
}
loading = false
listReachBottom() {
Taro.showLoading()
// 如果 loading 與視圖相關(guān),那它就應(yīng)該放在 `this.state` 里
// 我們這里使用的是一個(gè)同步的 API 調(diào)用 loading,所以不需要
this.loading = true
setTimeout(() => {
const { data } = this.state
this.setState({
data: data.concat(buildData(data.length))
}, () => {
this.loading = false;
Taro.hideLoading()
})
}, 1000)
}
render() {
const { data } = this.state
const dataLen = data.length
const itemSize = 100
return (
<VirtualList
className='List'
height={500}
itemData={data}
itemCount={dataLen}
itemSize={itemSize}
width='100%'
onScroll={({ scrollDirection, scrollOffset }) => {
if (
// 避免重復(fù)加載數(shù)據(jù)
!this.loading &&
// 只有往前滾動(dòng)我們才觸發(fā)
scrollDirection === 'forward' &&
// 5 = (列表高度 / 單項(xiàng)列表高度)
// 100 = 滾動(dòng)提前加載量,可根據(jù)樣式情況調(diào)整
scrollOffset > ((dataLen - 5) * itemSize + 100)
) {
this.listReachBottom()
}
}}
>
{Row}
</VirtualList>
);
}
}
將要渲染的列表單項(xiàng)組件。組件的 props
有 4 個(gè)屬性:
style
: 單項(xiàng)的樣式,樣式必須傳入組件的 style
中data
: 組件渲染的數(shù)據(jù)index
: 組件渲染數(shù)據(jù)的索引isScrolling
: 組件是否正在滾動(dòng),當(dāng) useIsScrolling
值為 true
時(shí)返回布爾值,否則返回 undefined
推薦使用 React.memo
或 React.PureComponent
或使用 shouldComponentUpdate()
來優(yōu)化此組件,避免不必要的渲染。
列表的長度。必填。
渲染數(shù)據(jù)。必填。
列表單項(xiàng)的大小,垂直滾動(dòng)時(shí)為高度,水平滾動(dòng)時(shí)為寬度。必填。
列表的高度。當(dāng)滾動(dòng)方向?yàn)榇怪睍r(shí)必填。
列表的寬度。當(dāng)滾動(dòng)方向?yàn)樗綍r(shí)必填。
根組件 CSS 類
根組件的樣式
初始滾動(dòng)偏移值,水平滾動(dòng)影響 scrollLeft
,垂直滾動(dòng)影響 scrollTop
。
列表內(nèi)部容器組件類型,默認(rèn)值為 View
。此容器的 parentNode
是 ScrollView
,childNodes
是列表。
列表內(nèi)部容器組件的 ref。
滾動(dòng)方向。vertical
為垂直滾動(dòng),horizontal
為平行滾動(dòng)。默認(rèn)為 vertical
。
列表滾動(dòng)時(shí)調(diào)用函數(shù),函數(shù)的第一個(gè)參數(shù)為對(duì)象,由三個(gè)屬性構(gòu)成:
scrollDirection
,滾動(dòng)方向,可能值為 forward
往前, backward
往后。scrollOffset
,滾動(dòng)距離scrollUpdateWasRequested
, 當(dāng)滾動(dòng)是由 scrollTo()
或 scrollToItem()
調(diào)用時(shí)返回 true
,否則返回 false
調(diào)用平臺(tái)原生的滾動(dòng)監(jiān)聽函數(shù)。
在可視區(qū)域之外渲染的列表單項(xiàng)數(shù)量,值設(shè)置得越高,快速滾動(dòng)時(shí)出現(xiàn)白屏的概率就越小,相應(yīng)地,每次滾動(dòng)的性能會(huì)變得越差。
是否注入 isScrolling
屬性到 children
組件。這個(gè)參數(shù)一般用于實(shí)現(xiàn)滾動(dòng)骨架屏(或其它 placeholder) 時(shí)比較有用。
除了以上參數(shù),所有 ScrollView
組件的參數(shù)都可以傳入 VirtualList
組件,沖突時(shí)優(yōu)先使用以上文檔描述的參數(shù)。
通過 React.createRef()
創(chuàng)建 ref,掛載到 VirtualList
上可以訪問到 VirtualList
的內(nèi)部方法:
export default class Index extends Component {
state = {
data: buildData(0),
}
list = React.createRef()
componentDidMount() {
const list = this.list.current
list.scrollTo()
list.scrollToItem()
}
render() {
const { data } = this.state
const dataLen = data.length
return (
<VirtualList
height={500} /* 列表的高度 */
width='100%' /* 列表的寬度 */
itemData={data} /* 渲染列表數(shù)據(jù) */
itemCount={dataLen} /* 渲染列表的長度 */
itemSize={100} /* 列表單項(xiàng)的高度 */
ref={this.list}
>
{Row} /* 列表單項(xiàng)組件,這里只能傳入一個(gè)組件 */
</VirtualList>
);
}
}
滾動(dòng)到指定的地點(diǎn)。
滾動(dòng)到指定的條目。
第二參數(shù) align
的值可能為:
auto
: 盡可能滾動(dòng)距離最小保證條目在可視區(qū)域中,如果已經(jīng)在可視區(qū)域,就不滾動(dòng)smart
: 條目如果已經(jīng)在可視區(qū)域,就不滾動(dòng);如果有部分在可視區(qū)域,盡可能滾動(dòng)距離最小保證條目在可視區(qū)域中;如果條目完全不在可視區(qū)域,那就滾動(dòng)到條目在可視區(qū)域居中顯示center
: 讓條目在可視區(qū)域居中顯示end
: 讓條目在可視區(qū)域末尾顯示start
: 讓條目在可視區(qū)域末尾顯示在 Vue 中使用虛擬列表,我們需要在入口文件聲明使用:
// app.js 入口文件
import Vue from 'vue'
import VirtualList from `@tarojs/components/virtual-list`
Vue.use(VirtualList)
一個(gè)最簡單的長列表組件會(huì)像這樣,virtual-list
的 5 個(gè)屬性都是必填項(xiàng):
<! –– row.vue 單項(xiàng)組件 ––>
<template>
<view
:class="index % 2 ? 'ListItemOdd' : 'ListItemEven'"
:style="css"
>
Row {{ index }}
</view>
</template>
<script>
export default {
props: ['index', 'data', 'css']
}
</script>
<! –– page.vue 頁面組件 ––>
<template>
<virtual-list
wclass="List"
:height="500"
:item-data="list"
:item-count="list.length"
:item-size="100"
:item="Row"
width="100%"
/>
</template>
<script>
import Row from './row.vue'
function buildData (offset = 0) {
return Array(100).fill(0).map((_, i) => i + offset)
}
export default {
data() {
return {
Row,
list: buildData(0)
}
},
}
</script>
實(shí)現(xiàn)無限滾動(dòng)也非常簡單,我們只需要在列表滾動(dòng)到底部時(shí),往列表尾部追加數(shù)據(jù)即可:
<template>
<virtual-list
wclass="List"
:height="500"
:item-data="list"
:item-count="dataLen"
:item-size="itemHeight"
:item="Row"
width="100%"
@scroll="onScroll"
/>
</template>
<script>
import Row from './row.vue'
function buildData (offset = 0) {
return Array(100).fill(0).map((_, i) => i + offset)
}
export default {
data() {
return {
Row,
list: buildData(0),
loading: false,
itemHeight: 100
}
},
computed: {
dataLen () {
return this.list.length
}
},
methods: {
listReachBottom() {
Taro.showLoading()
this.loading = true
setTimeout(() => {
const { data } = this.state
this.setState({
data: data.concat(buildData(data.length))
}, () => {
this.loading = false;
Taro.hideLoading()
})
}, 1000)
},
onScroll({ scrollDirection, scrollOffset }) {
if (
// 避免重復(fù)加載數(shù)據(jù)
!this.loading &&
// 只有往前滾動(dòng)我們才觸發(fā)
scrollDirection === 'forward' &&
// 5 = (列表高度 / 單項(xiàng)列表高度)
// 100 = 滾動(dòng)提前加載量,可根據(jù)樣式情況調(diào)整
scrollOffset > ((this.dataLen - 5) * this.itemHeight + 100)
) {
this.listReachBottom()
}
}
}
}
</script>
將要渲染的列表單項(xiàng)組件。組件的 props
有 4 個(gè)屬性:
css
: 單項(xiàng)的樣式,樣式必須傳入組件的 style
中data
: 組件渲染的數(shù)據(jù)index
: 組件渲染數(shù)據(jù)的索引isScrolling
: 組件是否正在滾動(dòng),當(dāng) useIsScrolling
值為 true
時(shí)返回布爾值,否則返回 undefined
列表的長度。必填。
渲染數(shù)據(jù)。必填。
列表單項(xiàng)的大小,垂直滾動(dòng)時(shí)為高度,水平滾動(dòng)時(shí)為寬度。必填。
列表的高度。當(dāng)滾動(dòng)方向?yàn)榇怪睍r(shí)必填。
列表的寬度。當(dāng)滾動(dòng)方向?yàn)樗綍r(shí)必填。
根組件 CSS 類
根組件的樣式
初始滾動(dòng)偏移值,水平滾動(dòng)影響 scrollLeft
,垂直滾動(dòng)影響 scrollTop
。
列表內(nèi)部容器組件類型,默認(rèn)值為 view
。此容器的 parentNode
是 scroll-view
,childNodes
是列表。
滾動(dòng)方向。vertical
為垂直滾動(dòng),horizontal
為平行滾動(dòng)。默認(rèn)為 vertical
。
列表滾動(dòng)時(shí)調(diào)用函數(shù),函數(shù)的第一個(gè)參數(shù)為對(duì)象,由三個(gè)屬性構(gòu)成:
scrollDirection
,滾動(dòng)方向,可能值為 forward
往前, backward
往后。scrollOffset
,滾動(dòng)距離scrollUpdateWasRequested
, 當(dāng)滾動(dòng)是由 scrollTo()
或 scrollToItem()
調(diào)用時(shí)返回 true
,否則返回 false
調(diào)用平臺(tái)原生的滾動(dòng)監(jiān)聽函數(shù)。注意調(diào)用傳遞此函數(shù)時(shí)使用的是 v-bind
而不是 v-on
:
<virtual-list
wclass="List"
:height="500"
:item-data="list"
:item-count="list.length"
:item-size="100"
:item="Row"
width="100%"
@scroll="onScroll"
:scroll-native="onScrollNative"
/>
在可視區(qū)域之外渲染的列表單項(xiàng)數(shù)量,值設(shè)置得越高,快速滾動(dòng)時(shí)出現(xiàn)白屏的概率就越小,相應(yīng)地,每次滾動(dòng)的性能會(huì)變得越差。
是否注入 isScrolling
屬性到 item
組件。這個(gè)參數(shù)一般用于實(shí)現(xiàn)滾動(dòng)骨架屏(或其它 placeholder) 時(shí)比較有用。
更多建議: