文件
OmniCard/mpuniapp/pages/template/batch-load-long-list/batch-load-long-list.uvue

197 行
5.5 KiB
Plaintext

<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 v-for="(item, index) in list" :key="index + '_' + item.id">
<view class="item">
<view><text>{{ item.name }}</text></view>
<view><text style="font-size: 12px; color: #999999">{{
item.info
}}</text></view>
</view>
</list-item>
</list-view>
</view>
</template>
<script setup lang="uts">
/**
* @description: 支持把超长列表数据拆分成多个批次创建,降低首屏一次性创建过多列表项的卡顿。
* @logic: 页面先准备完整大数据源,再把数据拆成批次任务;滚动和下拉刷新时控制批次任务的暂停与恢复,逐步把数据插入列表。
* @optimization: 通过任务队列和闲时分批插入机制延后列表项创建,避免 2000 条数据一次性进入渲染层影响滚动流畅度。
*/
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);
let scrolling = false;
const refresherTriggered = ref<boolean>(false);
let scrollendTimeout = -1;
let pulling = 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 = false;
// 滚动结束,继续执行分批加载逻辑
jobs.value.resume();
};
const onScroll = () => {
// 部分平台不支持scrollend事件,使用定时器模拟
clearTimeout(scrollendTimeout)
scrollendTimeout = setTimeout(() => {
onScrollEnd()
}, 100)
if (scrolling) {
return;
}
scrolling = 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) {
return
}
pulling = true
// 在下拉刷新时暂停分批加载,避免影响刷新操作
jobs.value.pause()
};
const onRefresherRestore = () => {
pulling = 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>