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++;
}

效果

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

结论

  • 未完待续......
相关推荐
好_快1 小时前
Lodash源码阅读-getMapData
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-MapCache
前端·javascript·源码阅读
萌萌哒草头将军2 小时前
🚀🚀🚀尤雨溪连发两条推特墙裂推荐的这些库你一定要知道!
前端·vue.js·react.js
混血哲谈2 小时前
webpack的SplitChunksPlugin和在路由或组件级别进行拆分
前端·webpack·node.js
木心操作2 小时前
webpack使用详细步骤
前端·webpack·node.js
烂蜻蜓3 小时前
深入理解 Vue 3 项目结构与运行机制
前端·javascript·vue.js
han_hanker4 小时前
一个普通的vue权限管理方案-菜单权限控制
前端·javascript·vue.js
Good Lucky4 小时前
防止重复点击方法总结-微信小程序
微信小程序·小程序
老大白菜5 小时前
lunar是一款无第三方依赖的公历 python调用
前端·python
混血哲谈7 小时前
如何使用webpack预加载 CSS 中定义的资源和预加载 CSS 文件
前端·css·webpack