Angular中HostListener监听滚动触底如何仅触发一次加载逻辑
问题原因
document:scroll是高频触发事件,用户滚动过程中每一次微小的页面位移都会触发回调执行。当前实现没有加加载状态控制,只要滚动位置满足触底条件,就会重复执行加载逻辑,前一批数据的请求还没返回时,后续触发的回调又会发起新的加载请求,最终导致所有数据被一次性拉取完成。
修复方案
核心是增加加载状态锁作为拦截条件,配合「无更多数据」的终止判断,从根本上避免重复触发加载,可选增加防抖优化滚动性能:
- 新增两个控制变量:
isLoading标记当前是否处于数据加载中,isNoMoreData标记是否已经加载完全部数据、没有下一页内容 - 滚动回调执行时先判断状态:如果正在加载、或者已经没有更多数据,直接终止逻辑不做后续判断
- 确认满足触底条件、准备发起加载请求前,先把
isLoading设为true上锁,直到本次请求完成(成功/失败都算)再把锁解开 - 每次加载完数据后判断返回结果,如果返回的数据条数小于单页加载条数,就把
isNoMoreData设为true,后续不再触发加载逻辑 - 可选优化:给滚动回调加200-300ms的防抖,减少高频位置计算带来的性能消耗
改造后参考代码
import { HostListener, Component, OnInit } from '@angular/core'; import { debounceTime, fromEvent } from 'rxjs'; @Component({ selector: 'app-list', templateUrl: './list.component.html' }) export class ListComponent implements OnInit { // 列表数据 list: any[] = []; // 加载状态锁 isLoading = false; // 无更多数据标记 isNoMoreData = false; // 分页参数 currentPage = 1; pageSize = 20; ngOnInit() { // 可选:用rxjs给滚动事件加300ms防抖,降低触发频率 fromEvent(window, 'scroll') .pipe(debounceTime(300)) .subscribe(() => this.handleScroll()); } // 如果不用上面的fromEvent防抖,直接保留@HostListener绑定即可 // @HostListener('document:scroll', []) handleScroll() { // 状态拦截:正在加载/无更多数据时直接返回 if (this.isLoading || this.isNoMoreData) return; // 触底判断:距离底部100px时就触发加载,优化用户体验 const isReachBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight - 100; if (isReachBottom) { this.loadNextPageData(); } } loadNextPageData() { // 发起请求前先上锁 this.isLoading = true; // 替换成你实际的接口请求逻辑 this.dataService.getList(this.currentPage, this.pageSize).subscribe({ next: (res) => { this.list.push(...res.data); this.currentPage++; // 返回数据不足一页时,标记为无更多数据 if (res.data.length < this.pageSize) { this.isNoMoreData = true; } }, error: (err) => { console.error('数据加载失败', err); }, complete: () => { // 请求结束后无论成功失败都解锁 this.isLoading = false; } }) } }
注意:防抖只能降低滚动回调的执行频率,无法完全避免重复触发,比如用户滚动停在触底阈值区间时,防抖后的回调依然可能多次执行,加载状态锁是必须实现的核心逻辑,不能只靠防抖解决问题。
内容的提问来源于stack exchange,提问作者silviaa11
相关产品推荐
相关产品推荐

