背景
代码千万行,安全第一行。 后端不分页,前端两行泪。
方案1.虚拟列表滚动
- 列表优化的常用方案,在web中有
ahooks
可用,uniapp中需要自己实现。
先写个简易virtual-list组件
- 首先需要给
list
和item
一个固定高度,可以计算出一屏内可以展示多少item
- 在
scroll-view
的滚动事件中,根据参数scrollTop
距离顶部的距离,计算出当前屏内展示的item
开始下标和结束下标 - 屏幕外不渲染的元素,可以使用空
view
撑开高度 - 注意事项
- 计算一屏展示多少
item
时需要预留一部分冗余空间,防止滚动太快出现白屏 - 我们已知的
list
和item
高度通常为rpx
单位,而@scroll
中的scrollTop
是px
单位,需要进行单位转换
- 计算一屏展示多少
vue
<template>
<scroll-view scrollY :style="{ height: containerHeight + 'rpx' }" @scroll="onScroll">
<view :style="paddingStyle">
<slot></slot>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { throttle } from "@/tools";
const props = withDefaults(
defineProps<{
// 所有数据
originalList: any[];
// 容器高度,单位rpx
containerHeight: number;
// 单个元素的高度,单位rpx
itemHeight: number;
onScroll?: (showList: any[]) => void;
}>(),
{
originalList: () => [],
containerHeight: 400,
itemHeight: 200,
},
);
// 滚动距离
const scrollTop = ref(0);
// 滚动事件
const onScroll = throttle((event: any) => {
scrollTop.value = event.detail.scrollTop;
}, 16);
const containerHeightPx = computed(() => {
return uni.upx2px(props.containerHeight);
});
const itemHeightPx = computed(() => {
return uni.upx2px(props.itemHeight);
});
// 一屏多少条数据
const showNum = computed(() => {
const bufferFactor = 2; // 缓冲因子
return ~~(containerHeightPx.value / itemHeightPx.value) + bufferFactor;
});
// 开始索引
const startIndex = computed(() => {
// 可见区域,第一个元素的index
const curIndex = ~~(scrollTop.value / itemHeightPx.value);
// console.log(showNum.value, itemHeightPx.value)
// 渲染区域第一个元素的index,这里缓冲区域的列表条数使用的是showNum.value
return curIndex < showNum.value ? 0 : curIndex - showNum.value;
});
// 渲染元素最后的index
const endIndex = computed(() => {
// 可见区域,第一个index
const curIndex = ~~(scrollTop.value / itemHeightPx.value);
let end = curIndex + showNum.value * 2; // 2倍是需要预留缓冲区域
let len = props.originalList.length;
return end >= len ? len : end; // 结束元素大于所有元素的长度时,就取元素长度
});
// 需要渲染的数据
const showList = computed(() => {
return props.originalList.slice(startIndex.value, endIndex.value);
});
watch(
showList,
() => {
props.onScroll?.(showList.value);
},
{ immediate: true },
);
// 空白占位的高度
const paddingStyle = computed(() => {
return {
paddingTop: startIndex.value * itemHeightPx.value + "px",
paddingBottom: (props.originalList.length - endIndex.value) * itemHeightPx.value + "px",
};
});
</script>
在项目中使用
vue
<VirtualList
:containerHeight="1000"
:originalList="originalList"
:itemHeight="400"
:onScroll="(list) => (showList = list)"
>
<view v-for="(item, index) in showList" :key="item.key">
</view>
</VirtualList>
ts
// 所有数据
const originalList = ref([])
// 在视图中渲染的数据
const showList = ref([])
效果
- 在支付宝小程序和微信小程序中符合预期,性能提升Nⁿ倍
- 在App、抖音小程序中页面会产生闪烁抖动现象,具体原因还未知
方案2.前端分页渲染
- 加个参数
currentPage
和pageSize
@scrolltolower
时给currentPage
+1originalList.slice(0, currentPage * pageSize)
即可?
改造代码
vue
<scroll-view
scrollY="true"
style="`height: 1000rpx`"
@scrolltolower="scrolltolower"
>
<view
v-for="item of originalList.slice(0, currentPage * pageSize)"
:key="item.id"
>
</view>
<uni-load-more :status="loadStatus"></uni-load-more>
<view class="h-20"></view>
</scroll-view>
ts
const currentPage = ref(1)
const pageSize = ref(5)
function scrolltolower() {
if (loadStatus.value === "nomore") return;
currentPage.value++;
}
效果
- 优点:
- 非常简单的原理、代码优化了首屏渲染性能
- 肉眼可见的不会产生任何兼容性问题、列表闪烁抖动等问题
- 缺点:
- 只能优化首屏的渲染性能,在往下滚动较多的时候,性能会没有虚拟滚动的好。
结论
- 未完待续......