优化RxJS通用分页数据递归获取函数的类型定义
优化RxJS分页全量获取函数的泛型实现
当然可以简化泛型参数,同时让类型推断更自然、类型安全更完善。下面是优化后的实现思路和代码:
核心优化点
- 合并泛型参数:不再单独声明
U,而是通过数据字段的键K从响应类型T中自动推断数组元素的类型,避免冗余泛型。 - 强化类型约束:明确要求响应类型
T必须包含可选的nextToken字段,以及至少一个数组类型的数据字段,减少类型断言的使用。 - 简化类型推断:利用TypeScript的条件类型
infer,直接从T[K]中提取数组元素类型,让返回值类型自动匹配。
优化后的代码
import { Observable, EMPTY } from 'rxjs'; import { expand, reduce } from 'rxjs/operators'; const recursivelyFetchAll = < T extends { nextToken?: string } & Record<string, unknown[]>, K extends keyof T >( request$: (nextToken?: string) => Observable<T>, dataKey: K ): Observable<T[K] extends (infer U)[] ? U[] : never> => request$().pipe( // 递归请求直到没有nextToken expand(({ nextToken }) => nextToken ? request$(nextToken) : EMPTY), // 合并所有分页的数据 reduce((acc, curr) => [...acc, ...curr[dataKey]], [] as T[K]) );
代码说明
- 泛型约束:
T extends { nextToken?: string } & Record<string, unknown[]>确保传入的响应类型必须有nextToken(可选),且至少有一个数组类型的字段。 - 自动推断元素类型:
T[K] extends (infer U)[] ? U[] : never会自动识别dataKey对应的数组字段,并提取其元素类型U,最终返回Observable<U[]>。 - 去掉冗余断言:原代码中的
as U[]被替换为[] as T[K],类型更精确,因为T[K]本身就是数组类型。
使用示例
假设你有两个接口的响应类型:
// /accounts 接口响应 type AccountRecord = { id: string; name: string }; type AccountsResponse = { accounts: AccountRecord[]; nextToken?: string }; // /resources 接口响应 type ResourceRecord = { id: string; type: string }; type ResourcesResponse = { resources: ResourceRecord[]; nextToken?: string };
调用通用函数时,类型会自动推断:
// 获取所有账户 const fetchAccounts = (nextToken?: string): Observable<AccountsResponse> => { // 实际API请求逻辑 }; const allAccounts$ = recursivelyFetchAll(fetchAccounts, 'accounts'); // allAccounts$ 类型为 Observable<AccountRecord[]> // 获取所有资源 const fetchResources = (nextToken?: string): Observable<ResourcesResponse> => { // 实际API请求逻辑 }; const allResources$ = recursivelyFetchAll(fetchResources, 'resources'); // allResources$ 类型为 Observable<ResourceRecord[]>
进一步扩展(可选)
如果以后遇到nextToken字段名不同的接口,还可以把nextToken的键也作为参数传入,让函数更通用:
const recursivelyFetchAll = < T extends Record<string, unknown> & Record<string, unknown[]>, D extends keyof T, N extends keyof T >( request$: (cursor?: string) => Observable<T>, dataKey: D, cursorKey: N ): Observable<T[D] extends (infer U)[] ? U[] : never> => request$().pipe( expand((response) => response[cursorKey] ? request$(response[cursorKey] as string) : EMPTY), reduce((acc, curr) => [...acc, ...curr[dataKey]], [] as T[D]) ); // 使用示例(假设接口用cursor代替nextToken) const allItems$ = recursivelyFetchAll(fetchItems, 'items', 'cursor');
内容的提问来源于stack exchange,提问作者Aurelia Peters
相关产品推荐
相关产品推荐

