uniapp 列表渲染性能优化实践

背景

代码千万行,安全第一行。 后端不分页,前端两行泪。

方案1.虚拟列表滚动

  • 列表优化的常用方案,在web中有ahooks可用,uniapp中需要自己实现。

先写个简易virtual-list组件

  • 首先需要给listitem一个固定高度,可以计算出一屏内可以展示多少item
  • scroll-view的滚动事件中,根据参数scrollTop距离顶部的距离,计算出当前屏内展示的item开始下标和结束下标
  • 屏幕外不渲染的元素,可以使用空view撑开高度
  • 注意事项
    • 计算一屏展示多少item时需要预留一部分冗余空间,防止滚动太快出现白屏
    • 我们已知的listitem高度通常为rpx单位,而@scroll中的scrollToppx单位,需要进行单位转换
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.前端分页渲染

  • 加个参数currentPagepageSize
  • @scrolltolower时给currentPage+1
  • originalList.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++;
}

效果

  • 优点:
    • 非常简单的原理、代码优化了首屏渲染性能
    • 肉眼可见的不会产生任何兼容性问题、列表闪烁抖动等问题
  • 缺点:
    • 只能优化首屏的渲染性能,在往下滚动较多的时候,性能会没有虚拟滚动的好。

结论

  • 未完待续......
相关推荐
ℋᙚᵐⁱᒻᵉ鲸落4 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
默_笙5 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
前端snow6 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上7 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
星栈7 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香7 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest7 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士8 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见9 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain
kyriewen9 小时前
面试官让我用 AI 重构一个 8 年陈的 React 组件——他说他不看代码,只看我会不会拆
前端·javascript·面试