RxJS 实战:使用 expand 操作符实现链式递归请求
概述
在实际开发中,我们经常需要处理分页数据。有时候,我们需要一次性加载多页数据,而不是等待用户点击"下一页"。本章将介绍如何使用 RxJS 的 expand 操作符来实现链式递归请求,自动加载多页数据。
expand 操作符简介
expand 是 RxJS 中一个强大的操作符,它可以将一个 Observable 的值递归地展开为新的 Observable。每次展开时,我们可以根据当前值决定是否继续递归,或者返回 EMPTY 来终止递归。
基本语法
typescript
source$.pipe(
expand((value, index) => {
// 根据 value 和 index 决定是否继续
if (shouldContinue) {
return nextObservable$;
}
return EMPTY; // 终止递归
})
)
实战场景:递归加载分页数据
假设我们有一个分页 API,需要一次性加载前 10 页的数据。使用 expand 可以优雅地实现这个需求。
实现思路
- 发起第一页请求
- 使用
expand递归地请求下一页 - 当达到指定页数时,返回
EMPTY终止递归 - 使用
toArray()收集所有响应 - 合并所有响应的数据
核心代码
typescript
let currentPage = this.page;
const params = new HttpParams()
.set('page', currentPage.toString())
.set('pageSize', this.pageSize.toString());
this.http.get<ApiResponse>(this.apiUrl, { params })
.pipe(
expand((response, index) => {
console.log('expand条件: index', index);
// index 从 0 开始,所以 index >= 9 表示已经请求了10页(索引0-9)
if (index >= 9) {
return EMPTY; // 终止递归
}
// 递增页码
currentPage = currentPage + 1;
const nextParams = new HttpParams()
.set('page', currentPage.toString())
.set('pageSize', this.pageSize.toString());
return this.http.get<ApiResponse>(this.apiUrl, { params: nextParams });
}),
toArray(), // 收集所有响应
map((responses) => {
// 合并所有响应的数据
const allItems: DataItem[] = [];
let total = 0;
responses.forEach((response) => {
if (response.success) {
allItems.push(...response.data.items);
total = response.data.pagination.total;
}
});
return { items: allItems, total };
})
)
.subscribe({
next: (result) => {
this.listOfData = result.items;
this.total = result.total;
console.log(`已加载 ${result.items.length} 条数据`);
},
error: (error) => {
console.error('加载数据失败:', error);
}
});
关键点解析
1. expand 的 index 参数
expand 操作符的第二个参数 index 从 0 开始计数,表示当前是第几次展开(不包括初始值)。所以:
- 第一次展开:
index = 0(对应第 2 页) - 第二次展开:
index = 1(对应第 3 页) - ...
- 第九次展开:
index = 9(对应第 11 页)
因此,要加载 10 页数据,条件应该是 index >= 9。
2. 终止条件
当满足终止条件时,返回 EMPTY Observable,这会立即完成,不再继续递归。
3. toArray() 的作用
toArray() 会将 Observable 流中的所有值收集到一个数组中。这对于需要处理所有响应的情况非常有用。
4. 与 take() 的区别
虽然可以使用 take(10) 来限制请求次数,但更好的做法是在 expand 内部判断终止条件,这样可以更精确地控制递归逻辑。
优势
- 代码简洁 :使用
expand可以避免复杂的循环和 Promise 链 - 自动处理:递归逻辑由 RxJS 自动管理,无需手动维护状态
- 易于扩展:可以轻松修改终止条件,比如根据响应数据决定是否继续
注意事项
- 内存占用:如果递归次数过多,可能会占用大量内存,需要注意
- 错误处理:如果中间某个请求失败,整个流会中断,需要适当的错误处理
- 性能考虑 :递归请求是串行的,如果数据量大,可能需要考虑并发请求(使用
forkJoin)
总结
expand 操作符是处理递归请求场景的利器。通过它,我们可以优雅地实现链式递归请求,自动加载多页数据。在实际项目中,根据具体需求选择合适的策略,既能保证代码的可读性,又能满足性能要求。