Observable<Object[]>实现无限滚动:能否用skip/take优化渲染?
解决Angular Observable无限滚动全量重渲染问题
你的思路完全正确——用skip+take的思路优化数据流,再配合trackBy,就能避免全量重渲染、解决页面跳顶的问题。先说说你当前代码的核心问题,再给你具体的实现方案:
为什么当前代码会导致页面跳顶?
你每次调用onScroll()时,都会让sales这个Observable重新返回包含所有已加载元素的全新数组(_.take(data, this.take))。Angular的*ngFor默认会对比数组的引用,一旦数组引用变化,就会销毁所有现有组件、重新创建整个列表——这不仅性能差,还会导致滚动位置丢失、页面跳回顶部。
优化方案:累积数据流 + trackBy
我们需要调整两个关键点:
- 用RxJS的
scan操作符累积已加载的数据,每次只追加新条目,而非返回全量数组; - 给
*ngFor添加trackBy函数,让Angular能识别已有元素,避免重复渲染。
具体代码实现
第一步:添加trackBy函数(HTML + TS)
先给*ngFor加上trackBy,这是避免不必要重渲染的基础:
<div class="sales-container" infiniteScroll [infiniteScrollDistance]="2" [infiniteScrollThrottle]="50" (scrolled)="onScroll()"> <app-sales-item *ngFor="let sale of sales$ | async; trackBy: trackBySaleId" [sale]="sale" ></app-sales-item> </div>
在TS中实现trackBy(用销售记录的唯一ID作为标识,替换成你实际的字段):
trackBySaleId(index: number, sale: any): number { // 假设每条sale有唯一的id属性,根据你的实际数据调整 return sale.id; }
第二步:用RxJS重构无限滚动逻辑
我们用Subject触发加载事件,结合scan累积数据,确保每次只加载并追加新的10条:
import { Subject, switchMap, scan, startWith } from 'rxjs'; // 触发加载更多的信号源 private loadMoreTrigger$ = new Subject<void>(); // 每次加载的条数 private pageSize = 10; // 初始加载条数 private initialLoadCount = 10; public sales$: Observable<any[]>; ngOnInit(): void { this.sales$ = this.filteringObservable.pipe( // 每次filteringObservable更新(比如筛选条件变化),重置加载状态 switchMap(fullData => { // 先加载初始的10条 const initialData = fullData.slice(0, this.initialLoadCount); // 监听加载更多信号,累积数据 return this.loadMoreTrigger$.pipe( startWith(void 0), // 初始触发一次,确保加载第一页 scan((loadedData, _) => { const currentLoadedCount = loadedData.length; // 如果已经加载完所有数据,直接返回现有列表 if (currentLoadedCount >= fullData.length) { return loadedData; } // 计算要加载的新数据:从已加载的位置开始,取pageSize条 const newData = fullData.slice(currentLoadedCount, currentLoadedCount + this.pageSize); // 追加新数据到已加载列表 return [...loadedData, ...newData]; }, initialData) ); }) ); } onScroll(): void { // 触发加载更多 this.loadMoreTrigger$.next(); }
方案说明
- 累积数据流:通过
scan操作符,我们每次只把新加载的条目追加到已有列表中,返回的数组是在原数组基础上扩展的(而非全新的全量数组),*ngFor只会渲染新增的元素; - trackBy保障:即使数据流更新,Angular会通过
trackBy识别出已有元素,不会销毁重建这些组件,滚动位置自然不会丢失; - 适配实时更新:如果
filteringObservable是实时更新的(比如后端推送新数据),当fullData变化时,switchMap会自动重置加载状态,重新加载初始数据,也能兼容后续的加载更多操作。
内容的提问来源于stack exchange,提问作者Stan
相关产品推荐
相关产品推荐

