要在Vue中实现触底加载,主要可以通过以下几个步骤:1、监听滚动事件、2、判断是否触底、3、触发加载更多数据的函数。具体实现方法包括在组件中监听滚动事件,判断用户是否滚动到底部,并在触底时执行加载更多数据的函数。
一、监听滚动事件
1. onMounted
钩子函数
onMounted(() => {
// 步骤 1:获取监控报警列表
getMonitorAlarmList(true);
// 步骤 2:绑定滚动事件监听
if (scrollContainer.value) {
scrollContainer.value.addEventListener('scroll', handleScroll);
}
});
const scrollContainer = ref(null)
<div class="right-box" ref="scrollContainer" >
在div引用,滚动条监控就绑定到了这个div表中
- 功能说明
- 数据初始化:
getMonitorAlarmList(true)
在组件挂载后立即调用,通常用于异步请求数据并更新页面; - 事件绑定:通过模板引用
scrollContainer.value
获取 DOM 元素,并为其添加scroll
事件监听器,滚动时触发handleScroll
方法;
- 数据初始化:
- 关键细节
- 使用
if (scrollContainer.value)
确保 DOM 元素已渲染完成后再操作,避免空引用错误; - 滚动监听常用于实现“滚动加载更多”等动态交互功能。
- 使用
2. onUnmounted
钩子函数
onUnmounted(() => {
// 清除滚动事件监听
if (scrollContainer.value) {
scrollContainer.value.removeEventListener('scroll', handleScroll);
}
});
- 功能说明
- 资源清理:组件销毁前移除
scroll
事件监听,防止组件卸载后事件回调仍被触发,导致内存泄漏或意外行为;
- 资源清理:组件销毁前移除
- 关键细节
- 必须通过
removeEventListener
移除与添加时完全相同的函数引用(此处handleScroll
需保持一致性); - 即使组件被
v-if
隐藏或路由切换,也会触发onUnmounted
进行清理。
- 必须通过
二、判断是否触底
const handleScroll = throttle(() => {
if (!scrollContainer.value ||
monitorLoading.value ||
!hasMoreData.value) return
const container = scrollContainer.value
const { scrollTop, scrollHeight, clientHeight } = container
const scrollBottom = scrollHeight - (scrollTop + clientHeight)
console.log('--------------------------')
// 距离底部50px时触发加载
if (scrollBottom < 50 && scrollBottom > 0) {
loadPreviousData()
}
}, 200)
以下是对这段代码的逐行解析,结合滚动事件处理和节流技术实现原理:
一、代码结构概览
这段代码通过 **throttle
节流函数** 包装了一个滚动事件处理器 handleScroll
,核心目标是实现滚动到底部时加载历史数据的功能。代码逻辑可分为三部分:
- 节流控制:限制滚动事件触发频率
- 条件校验:过滤无效的滚动事件
- 滚动位置计算:判断是否需要加载数据
二、逐行详细解析
1. 节流函数包装
const handleScroll = throttle(() => {
// ...
}, 200)
- 技术背景
使用throttle
函数将实际处理逻辑包装为每 200ms 最多执行一次(根据搜索结果中的时间戳版节流实现)。 - 作用
避免高频滚动事件导致loadPreviousData
被频繁调用,降低性能消耗(如接口重复请求、DOM重复计算等)。 - 实现细节
若使用增强版节流实现(如搜索结果的首尾双触发
版本),能保证最后一次滚动必定触发加载。
2. 前置条件校验
if (!scrollContainer.value || monitorLoading.value || !hasMoreData.value) return
- 三个校验条件
- **
!scrollContainer.value
**:确保滚动容器 DOM 已正确挂载(常见于 Vue/React 的模板引用未初始化时) - **
monitorLoading.value
**:防止当前数据加载未完成时重复触发请求(类似防重提交锁) - **
!hasMoreData.value
**:无更多数据时停止加载(分页场景的终止条件)
- **
- 设计意义
通过逻辑短路 (return
) 提前过滤 75% 以上的无效事件,降低后续计算消耗。
3. 滚动容器参数提取
const container = scrollContainer.value
const { scrollTop, scrollHeight, clientHeight } = container
- 关键属性
- **
scrollTop
**:已滚动距离(从顶部开始计算) - **
scrollHeight
**:容器总高度(包括不可见区域) - **
clientHeight
**:容器可视区域高度
- **
- 参数关系
scrollTop + clientHeight
表示当前滚动位置底部在总高度的绝对坐标。
4. 滚动到底部判断
const scrollBottom = scrollHeight - (scrollTop + clientHeight)
if (scrollBottom < 50 && scrollBottom > 0) {
loadPreviousData()
}
- 计算逻辑
scrollBottom
表示距离容器底部的剩余未滚动距离,**<50px** 时触发加载(常见加载阈值设定)。 - 双重条件
- **
scrollBottom < 50
**:距离底部小于 50 像素时触发 - **
scrollBottom > 0
**:避免容器高度不足时误判(如内容未撑满容器)
- **
- 业务扩展
可调整阈值(如 100px)平衡用户体验与性能,或添加加载动画提示。
5. 调试输出(可选)
console.log('--------------------------')
- 作用
输出分隔线便于控制台观察事件触发频次,实际生产环境需移除。 - 调试技巧
可改为记录滚动位置:console.log('ScrollBottom:', scrollBottom)
。
三、综合技术要点
-
节流实现选择
若需精确控制首次/末次触发,建议采用增强版节流- 时间间隔(200ms)需根据接口响应速度调整,避免加载速度跟不上滚动触发频率
-
滚动容器类型
- 适用于
overflow: auto/scroll
的定高容器 - 若为窗口滚动,需改用
window.addEventListener
并计算document.documentElement
相关属性
- 适用于
-
性能优化
- 使用
IntersectionObserver API
可替代手动计算(更高效但兼容性需考虑) - 加载数据时禁用节流:
monitorLoading.value = true
期间节流函数自动跳过逻辑
- 使用
三、触发加载更多数据的函数
const loadPreviousData = () => {
const currentStart = moment(queryParamsTime.value[0])
const newEnd = currentStart.clone()
const newStart = currentStart.clone().subtract(5, 'minutes')
// 时间范围有效性验证
if (moment().diff(newStart, 'days') > 30) {
hasMoreData.value = false
return
}
// 保持显示范围为扩展后的时间(不修改结束时间)
displayTimeRange.value = [newStart.format('YYYY-MM-DD HH:mm'), displayTimeRange.value[1]]
queryParamsTime.value = [
newStart.format('YYYY-MM-DD HH:mm'),
newEnd.format('YYYY-MM-DD HH:mm')
]
getMonitorAlarmList(false)
}
一、函数功能概述
此函数用于在用户滚动加载历史数据时,动态扩展查询时间范围并触发数据请求,核心逻辑如下:
- 时间范围计算:每次向前扩展 5 分钟的时间窗口;
- 有效性校验:限制最多查询 30 天内的数据;
- 状态更新:同步更新界面显示时间和实际查询参数;
- 数据加载:调用 API 获取指定时间段的监控报警数据。
二、逐行代码解析
1. 时间范围初始化
const currentStart = moment(queryParamsTime.value[0]); // 当前查询的起始时间
const newEnd = currentStart.clone(); // 新时间段的结束时间(保持原起始时间不变)
const newStart = currentStart.clone().subtract(5, 'minutes'); // 新起始时间 = 当前起始时间 -5 分钟
- 逻辑说明
queryParamsTime.value[0]
是上一次请求的时间起点(如"2023-10-01 10:00"
);newEnd
保持与当前起始时间一致,形成 左闭右开 区间[newStart, newEnd)
;subtract(5, 'minutes')
将每次查询的时间窗口向前推移 5 分钟(类似分页查询的offset
)。queryParamsTime.value[0]
:获取一个数组中的第一个元素,该数组可能存储了时间相关的数据。moment(...)
:将获取的时间值转换为moment
对象,便于后续的时间操作(如格式化、计算等)。
2. 时间有效性校验
if (moment().diff(newStart, 'days') > 30) {
hasMoreData.value = false; // 标记无更多数据
return;
}
-
校验逻辑
moment().diff(newStart, 'days')
计算当前时间与newStart
的间隔天数;- 若
newStart
早于 30 天前,则禁止继续加载(假设系统仅保留 30 天内的数据); hasMoreData.value
用于控制 UI 显示(如隐藏“加载更多”按钮)。
-
潜在问题
- 时区敏感性:若
moment()
未显式指定时区,可能导致跨时区系统计算错误; - 边界条件:
diff
返回浮点数(如 30.1 天),建议改为>= 30
更严格。
- 时区敏感性:若
3. 更新显示时间范围
displayTimeRange.value = [
newStart.format('YYYY-MM-DD HH:mm'), // 新起始时间
displayTimeRange.value[1] // 保持原结束时间不变
];
- 作用
- 扩展界面显示的时间范围(例如从
10:00-10:05
变为09:55-10:05
); - 用户可直观看到加载了更早时间段的数据。
- 扩展界面显示的时间范围(例如从
4. 更新查询参数
queryParamsTime.value = [
newStart.format('YYYY-MM-DD HH:mm'), // 新起始时间
newEnd.format('YYYY-MM-DD HH:mm') // 新结束时间(原起始时间)
];
- 设计意图
- 确保下一次数据请求使用新的时间窗口;
- 后端接口可能根据
start
和end
参数筛选数据。
5. 触发数据加载
getMonitorAlarmList(false); // 参数 false 表示“加载更多”而非首次加载
- 参数设计
false
可能用于区分初始化加载和增量加载(例如不显示加载动画);- 需确保
getMonitorAlarmList
内部使用最新的queryParamsTime.value
。
三、潜在优化点
1. 时间计算精度
- 问题:硬编码
5 分钟
可能导致最后一页数据遗漏(如数据按 10 分钟分段); - 建议:根据实际数据颗粒度动态调整步长,或由接口返回下次查询的起始时间。
2. 时区处理
// 显式指定 UTC 时间
const currentStart = moment.utc(queryParamsTime.value[0]);
- 避免服务器与客户端时区不一致导致时间窗口错位。
3. 状态管理
- 问题:直接修改
hasMoreData.value
可能未考虑接口返回的实际数据量; - 建议:根据接口响应判断是否还有更多数据(如返回
isLastPage
标志)。
4. 防御性编程
// 确保时间字符串有效
if (!moment(queryParamsTime.value[0]).isValid()) return;
四、总结
此函数通过时间窗口滑动实现了按需加载历史数据,核心逻辑清晰但需注意:
- 时间计算的准确性与时区处理;
- 状态同步(显示时间 vs 查询参数);
- 扩展性(步长调整、数据分段逻辑)。
结合前文滚动事件处理,形成完整的分页加载交互闭环。
定义一个时间选择器
<el-form :model="queryParams" :inline="true" label-width="80px">
<el-form-item label="">
<el-date-picker v-model="displayTimeRange" type="datetimerange" start-placeholder="开始时间" end-placeholder="结束时间"
style="width:400px;" format="YYYY-MM-DD HH:mm" value-format="YYYY-MM-DD HH:mm" @change="handleTimeChange" />
</el-form-item>
</el-form>
每五分钟有一个时间间隔