<template>
<view style="flex: 1; background-color: aliceblue">
<view class="tips">list-view组件虽然在渲染层有recycle机制,但长列表的vnode/Element太多也会造成初始化卡顿。
本示例有2000条数据,通过分批创建列表项,减少初始化卡顿。并通过闲时创建机制避免影响列表滚动等UI操作</view>
<list-view style="flex: 1" :refresher-enabled="true" :refresher-triggered="refresherTriggered" @scroll="onScroll"
@scrolltolower="onScrollToLower" @refresherrefresh="onRefresh" @refresherpulling="onRefresherPulling" @refresherrestore="onRefresherRestore">
<list-item class="item" v-for="(item, index) in list" :key="index + '_' + item.id">
<view><text>{{ item.name }}</text></view>
<view><text style="font-size: 12px; color: #999999">{{
item.info
}}</text></view>
</list-item>
</list-view>
</view>
</template>
<script setup lang="ts">
type Item = {
id : number;
name : string;
info : string;
};
class Jobs {
private jobs : (() => Promise<void>)[] = [];
paused : boolean = true;
constructor() { }
add(job : () => Promise<void>) {
this.jobs.push(job);
}
pause() {
this.paused = true;
}
private execute() {
if (this.paused) {
return;
}
if (this.jobs.length == 0) {
this.paused = true
return;
}
const job = this.jobs.shift();
if (job != null) {
job().then(() => {
this.execute();
});
}
}
resume() {
if(!this.paused) {
return
}
this.paused = false;
setTimeout(() => {
this.execute();
}, 0)
}
reset() {
this.jobs = [];
this.paused = true;
}
get done() : boolean {
return this.jobs.length == 0;
}
}
function delay(time : number) : Promise<void> {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, time)
})
}
const bigList = ref<Item[]>([]);
const list = ref<Item[]>([]);
const jobs = ref<Jobs>(new Jobs());
const batchSize = ref<number>(100);
const scrolling = ref<boolean>(false);
const refresherTriggered = ref<boolean>(false);
const scrollendTimeout = ref<number>(-1);
const pulling = ref<boolean>(false);
function init(autoResumeJobs: boolean = true) {
// 将数据分批放入任务队列
const batchCount = Math.ceil(bigList.value.length / batchSize.value);
for (let i = 0; i < batchCount; i++) {
const start = i * batchSize.value;
const end = Math.min(start + batchSize.value, bigList.value.length);
jobs.value.add(async () => {
list.value.push(...bigList.value.slice(start, end));
// 两批数据之间增加延迟,防止卡顿时间太久
await nextTick();
await delay(100)
});
}
if(autoResumeJobs) {
jobs.value.resume();
}
}
const onScrollEnd = () => {
scrolling.value = false;
// 滚动结束,继续执行分批加载逻辑
jobs.value.resume();
};
const onScroll = () => {
// 部分平台不支持scrollend事件,使用定时器模拟
clearTimeout(scrollendTimeout.value)
scrollendTimeout.value = setTimeout(() => {
onScrollEnd()
}, 100)
if (scrolling.value) {
return;
}
scrolling.value = true;
// 滚动期间暂停分批加载,保证滚动流畅度
jobs.value.pause();
};
const onScrollToLower = () => {
// 分批加载进行中,暂不执行滚动到底部加载更多数据的逻辑
if (!jobs.value.done) {
return;
}
// 加载更多数据
};
const onRefresh = () => {
// 下拉刷新触发,重置任务队列
jobs.value.reset();
refresherTriggered.value = true
setTimeout(() => {
refresherTriggered.value = false;
list.value.splice(0, list.value.length);
/**
* refreshRestore事件会触发继续分批加载,此处init不自动调用resume。这样做能减少一些下拉刷新复位期间加载数据引发的卡顿。
* TODO 清空列表也会导致下拉刷新复位时发生卡顿,后续再优化
*/
init(false);
}, 500)
};
const onRefresherPulling = () => {
if(pulling.value) {
return
}
pulling.value = true
// 在下拉刷新时暂停分批加载,避免影响刷新操作
jobs.value.pause()
};
const onRefresherRestore = () => {
pulling.value = false
// 下拉刷新结束后恢复分批加载
jobs.value.resume();
};
// 模拟大列表数据 - 相当于 created 生命周期
for (let i = 0; i < 2000; i++) {
bigList.value.push({
id: i,
name: `Wifi_` + i,
info: `信号强度: -${Math.floor(Math.random() * 60) + 40
} db, 安全性: WPA/WPA2/WPA3-Personal`,
} as Item);
}
onReady(() => {
init();
});
</script>
<style>
.tips {
margin: 10px;
border-radius: 5px;
padding: 10px;
background-color: white;
}
.item {
margin: 5px 10px;
padding: 10px;
border-radius: 5px;
background-color: white;
}
</style>