如何让TypeScript泛型ApiCall函数根据泛型类型执行不同逻辑?
在TypeScript中处理API返回的数组/单对象兼容问题
解决方案思路
要解决这个问题,核心是在编译时区分泛型参数是否为数组类型,并在运行时自动转换结果格式,同时保持原有调用方式不变。TypeScript虽然没有C++式的模板特化,但可以通过条件类型+运行时类型守卫的组合实现需求。
具体实现步骤
1. 定义辅助类型(提取数组元素类型)
先定义一个条件类型,用来从数组类型中提取元素类型,方便后续处理:
// 提取数组的元素类型,非数组类型则返回原类型 type ElementOf<T> = T extends (infer U)[] ? U : T;
2. 修改ApiCall函数
利用条件类型声明原始API返回的可能类型(单个对象或数组),再通过运行时判断自动转换格式:
export async function ApiCall<T>(funcName: string, args?: ArgList): Promise<T> { // 调用实际API,原始结果可能是单个对象或数组(根据泛型T是否为数组) const rawResult = await actualApiCall(funcName, args) as T extends any[] ? ElementOf<T> | T : T; // 运行时处理:如果期望返回数组,且结果是单个对象,则包装为数组 if (!Array.isArray(rawResult)) { // 类型断言告诉编译器:当T是数组时,此转换安全 return [rawResult] as T; } // 已经是数组,直接返回 return rawResult as T; }
3. 可选:添加函数重载增强类型提示
为了让调用者获得更清晰的类型提示,可以添加函数重载签名:
// 重载1:请求非数组类型 export async function ApiCall<T>(funcName: string, args?: ArgList): Promise<T>; // 重载2:请求数组类型 export async function ApiCall<T>(funcName: string, args?: ArgList): Promise<T[]>; // 实际实现(保持不变) export async function ApiCall<T>(funcName: string, args?: ArgList): Promise<T> { const rawResult = await actualApiCall(funcName, args) as T extends any[] ? ElementOf<T> | T : T; if (!Array.isArray(rawResult)) { return [rawResult] as T; } return rawResult as T; }
效果验证
原有调用方式完全不变,编译器会自动处理类型:
// 场景1:请求单个对象,直接返回MyType类型 ApiCall<MyType>("foo").then((myTypeObj) => { const x = myTypeObj.myField; // 类型安全,不会报错 }); // 场景2:请求数组,自动将单个对象转为数组,返回MyType[]类型 ApiCall<MyType[]>("bar").then((array) => { for (let i = 0; i < array.length; ++i) { // 数组操作完全正常,无需关心API返回的是单个对象还是数组 } });
方案优势
- 无侵入性:调用者无需修改任何代码,保持原有调用习惯。
- 类型安全:编译时严格检查类型,避免运行时错误。
- 自动兼容:运行时自动处理API的异常返回格式,无需调用者感知。
内容的提问来源于stack exchange,提问作者Abstraction
相关产品推荐
相关产品推荐

