Vue3 封装不定高虚拟列表 hooks,复用性更好!

某日某时某刻某分某秒,收到 小 A 同学 的消息,原因是他司有人反馈某项目中页面渲染内容太慢、太卡,且后端开发也贴出接口响应很快的日志,于是乎这个 优化 的小任务就落到了他头上。

经过简单询问得知:

  • 页面上某个 table 组件 渲染的数据 不是分页的,接口将查到的所有符合的数据一股脑返回给了前端,约几万条数据

  • 前端页面表现是 渲染慢、交互卡

模拟效果(渲染 3w 数据)如下:

图片

治标不治本 — 滚动加载

当然 小 A 同学 很快就想到了自己实现滚动加载:

  • 每次渲染20条数据,当滚动条 触底后继续渲染

于是马上进行提测,而测试同学也非常的敬业,一直滚动加载到了 几千条 数据,此时虽然在渲染表格项的时候没有出现卡顿,但是点击表格项时需要弹窗的这个交互,却又开始卡顿了,模拟效果如下(此处省略分批渲染):

图片

table 慢元素

由于 table 元素在渲染时需要 更多的计算资源,这其中需要计算表格的布局、单元格的大小和位置等,这可能会导致在 某些情况 下 table 元素的渲染速度较慢,因此 table 元素也叫 慢元素。

现在的问题显然由于使用 慢元素渲染大数据 而造成渲染卡顿、交互不流畅的问题,而前面的 分页加载 虽然可以解决 前期渲染卡顿 的问题,却不能解决 后期弹窗交互卡顿 的问题,原因就是 最后实际需要渲染的慢元素根本没有减少

那有什么办法能 保证每次实际渲染的数量不会递增 呢?

有,就是 只渲染可视区及其周边的数据,而这也就是 虚拟列表 的核心。

虚拟列表

接下来我们会封装一个和虚拟列表相关的 hooks,不封装成组件的目的就是为了让此方法更加的通用,不局限外部使用的第三方组件或自己封装的组件,让其既支持 table 形式,又让其支持普通的 list 形式,还能让其支持 select 形式

虚拟列表 — 定高

要实现虚拟列表需要考虑如下三个方面:

  • 滚动模拟

    • 普通列表渲染 是 可滚动 的,滚动产生的条件就是 每次渲染数量会递增,那么 虚拟列表 就需要在保证 每次渲染数量不递增 的情况下 支持滚动

  • 渲染正确的内容

    • 保证用户在向上或向下滚动的过程中数据的 渲染内容是正确的,只有这样看起来才和 普通列表 表现一致

  • 渲染的数据需要在可视区

    • 虚拟列表 支持滚动之后,就需要保证渲染的数据一直存在于 可视区,而不是随着滚动到可视区之外

这里在引入三个名称和配图,方便进行理解,具体如下:

  • 滚动容器

    • 顾名思义,就是为了实现滚动,所以需要设置 height 固定高度 或 最大高度 max-height

  • 渲染实际高度的容器

    • 为了实现模拟滚动,需要将实际高度的值,即 每个列表项高度之和 设置在某个元素上,这样就可以超过 滚动容器的高度,从而产生滚动效果

  • 偏移容器

    • 要实现渲染的数据始终处于可视区,那么可以针对 包裹着所有列表项的元素 进行处理,也就是将它的 transform: translateY(n) 值设置为 当前已滚动的高度 scrollTop 即可

    • 同时要保证每个滚动位置要渲染正确的数据,那么最简单的方式就是,根据 当前已滚动的高度 scrollTop 除以 单个列表项的高低 height,计算出当前需要渲染的 起始索引 startIndex,假设每次需要渲染 20 条 数据,很容易算出 结束索引 endIndex,这样就可以知道当前滚动位置需要渲染的数据范围是什么

图片

不到 100 行即可拥有虚拟滚动,具体实现如下:

// useVirtualList.ts
import { ref, onMounted, onBeforeUnmount, watch, computed} from "vue";
import type { Ref } from "vue";
interface Config {
    data: Ref<any[]>; // 数据  itemHeight: number;// 列表项高度  size: number;// 每次渲染数据量  scrollContainer: string;// 滚动容器的元素选择器  actualHeightContainer: string;// 用于撑开高度的元素选择器  tranlateContaine
### Vue3封装用于列表查询的 HooksVue3 的开发过程中,通过组合 API 和自定义 Hook 可以有效地提代码可读性和复用性。对于列表查询功能而言,可以创建一个 `useListQuery` hook 来处理数据获取、分页以及过滤等功能。 #### 创建 useListQuery Hook 此钩子主要负责管理与服务器交互的状态,并提供相应的方法给组件调用: ```javascript import { ref, computed } from &#39;vue&#39;; export function useListQuery(fetchDataFn) { const loading = ref(false); const error = ref(null); const data = ref([]); async function fetchData(params = {}) { try { loading.value = true; const response = await fetchDataFn(params); // 调用传入的数据请求方法 data.value = response.data || []; } catch (err) { error.value = err.message; } finally { loading.value = false; } } return { loading, error, data, fetchData }; } ``` 上述实现中,`fetchDataFn` 是由外部传递进来的一个异步函数,它应该返回包含所需数据的对象。该 Hook 提供了一个简单的接口来加载远程资源并跟踪其状态[^1]。 #### 使用示例 假设有一个名为 `getUsers` 的服务端 API 请求函数用来获取用户列表,则可以在页面组件内这样使用这个 Hook: ```javascript <template> <div v-if="loading">Loading...</div> <ul v-else> <li v-for="(item,index) in users" :key="index">{{ item.name }}</li> </ul> </template> <script setup> import { onMounted } from "vue"; import { useListQuery } from "@/hooks/useListQuery"; import getUsers from &#39;@/services/userService&#39;; const { loading, error, data:users , fetchData} = useListQuery(getUsers); onMounted(() => { fetchData(); }); </script> ``` 这段模板展示了如何利用刚刚编写的 Hook 进行基本操作——当组件挂载完成后自动发起一次网络请求;如果正在等待响应则显示提示信息;一旦接收到有效回复便渲染出相应的 HTML 结构[^2]。 为了进一步增强实用性,在实际项目里还可以考虑加入更多特性比如缓存机制、错误重试策略或是支持参数化查询条件等级选项[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_35430208

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值