You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化RxJS通用分页数据递归获取函数的类型定义

优化RxJS分页全量获取函数的泛型实现

当然可以简化泛型参数,同时让类型推断更自然、类型安全更完善。下面是优化后的实现思路和代码:

核心优化点

  1. 合并泛型参数:不再单独声明U,而是通过数据字段的键K从响应类型T中自动推断数组元素的类型,避免冗余泛型。
  2. 强化类型约束:明确要求响应类型T必须包含可选的nextToken字段,以及至少一个数组类型的数据字段,减少类型断言的使用。
  3. 简化类型推断:利用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 11:38:28