RxJS如何实现多接口数据随到达逐个渲染 无需等待全部请求完成
问题原因
你之前的两种写法无法实现即时渲染,核心是操作符本身的机制限制:
- 嵌套
mergeMap的写法是顺序串行订阅请求:必须等s1$返回才会发起s2$,等s2$返回才会发起s3$,最终要等三个请求全部完成才会吐出组合后的对象,本质是串行请求,和forkJoin表现一致。 - 原生
combineLatest要求所有传入的Observable都至少发射一次值,才会开始输出组合结果,三个HTTP请求都是单次发射的流,自然也要等最慢的请求返回后才会输出第一个值。
实现方案
两种方案都可以实现「哪个请求先返回就先渲染对应块」的效果,按需选择即可。
方案1:增量更新组合对象(改动最小)
给每个请求流加初始空值占位,让combineLatest在订阅后立刻发射初始值,后续每有一个请求返回,就自动发射更新后的组合对象,不需要等其他请求完成。
TS侧代码修改:
import { startWith } from 'rxjs/operators'; constructor(route: ActivatedRoute) { this.pageData$ = route.params.pipe( switchMap(params => this.getPageData$(params['id'])) ); } private getPageData$(eventId: string): Observable<EventGroupPageData> { return combineLatest([ s1$.pipe(startWith(null)), s2$.pipe(startWith(null)), s3$.pipe(startWith(null)) ]).pipe( map(([data1, data2, data3]) => ({data1, data2, data3})) ); }
模板侧针对每个数据块单独判断,有值再渲染,无值显示加载态:
<div *ngIf="pageData$ | async as pageData"> <!-- 第一块内容,s1返回即渲染 --> <ng-container *ngIf="pageData.data1; else loadingS1"> <div>渲染data1对应的UI内容</div> </ng-container> <ng-template #loadingS1><div>第一块数据加载中...</div></ng-template> <!-- 第二块内容,s2返回即渲染 --> <ng-container *ngIf="pageData.data2; else loadingS2"> <div>渲染data2对应的UI内容</div> </ng-container> <ng-template #loadingS2><div>第二块数据加载中...</div></ng-template> <!-- 第三块内容,s3返回即渲染 --> <ng-container *ngIf="pageData.data3; else loadingS3"> <div>渲染data3对应的UI内容</div> </ng-container> <ng-template #loadingS3><div>第三块数据加载中...</div></ng-template> </div>
如果担心模板中多次取值导致的变更检测问题,可以给pageData$加shareReplay(1)缓存最新值。
方案2:模板独立订阅每个流(耦合度最低)
三个独立API请求本身没有依赖关系,可以直接在模板中分别用async管道订阅,每个请求独立控制渲染逻辑,完全不需要在TS侧做组合。
TS侧直接暴露三个请求流即可,路由参数变化时同步重新发起请求:
import { shareReplay, distinctUntilChanged } from 'rxjs/operators'; constructor(route: ActivatedRoute) { const dataStream$ = route.params.pipe( switchMap(params => { // 这里根据路由参数初始化三个请求,加shareReplay避免重复订阅发请求 return combineLatest([ s1$.pipe(startWith(null), shareReplay(1)), s2$.pipe(startWith(null), shareReplay(1)), s3$.pipe(startWith(null), shareReplay(1)) ]) }), map(([data1, data2, data3]) => ({data1, data2, data3})), shareReplay(1) ); // 拆分三个独立的流暴露给模板 this.data1$ = dataStream$.pipe(map(d => d.data1), distinctUntilChanged()); this.data2$ = dataStream$.pipe(map(d => d.data2), distinctUntilChanged()); this.data3$ = dataStream$.pipe(map(d => d.data3), distinctUntilChanged()); }
模板侧独立订阅每个流:
<!-- 第一块独立渲染 --> <ng-container *ngIf="data1$ | async as data1; else loading1"> <div>渲染data1内容</div> </ng-container> <ng-template #loading1><div>加载中...</div></ng-template> <!-- 第二块独立渲染 --> <ng-container *ngIf="data2$ | async as data2; else loading2"> <div>渲染data2内容</div> </ng-container> <ng-template #loading2><div>加载中...</div></ng-template> <!-- 第三块独立渲染 --> <ng-container *ngIf="data3$ | async as data3; else loading3"> <div>渲染data3内容</div> </ng-container> <ng-template #loading3><div>加载中...</div></ng-template>
额外提示
- 三个无依赖的API请求不要用
mergeMap/concatMap嵌套串行发起,会额外增加整体加载时间,用combineLatest是并行发起请求,性能更好。 - 如果请求有错误处理需求,给每个独立的请求流加
catchError,返回对应错误状态即可,不会因为单个请求失败阻塞其他内容渲染。
内容的提问来源于stack exchange,提问作者dzenesiz
相关产品推荐
相关产品推荐

