TypeScript中如何基于字符串字面量收窄API响应类型?
解决TypeScript中根据函数参数收窄API响应类型的问题
你的思路方向是对的——联合类型是处理这类多形态响应的基础,但问题出在函数参数和响应类型之间没有建立明确的关联,TypeScript没办法自动把传入的fruit值和FruitResponse的具体分支对应起来。我们可以通过泛型绑定来解决这个问题,让类型系统明确知道:当你传入'apples'时,响应一定包含apples字段;传入'oranges'时则包含oranges字段。
具体实现步骤
1. 先定义水果资源的类型映射
首先我们把每个水果对应的资源类型整理成一个映射类型,方便后续复用和扩展:
// 这里用object示例,你可以替换成实际的资源类型 type FruitResources = { apples: Array<object>; oranges: Array<object>; };
2. 定义泛型化的响应类型
接下来创建一个泛型类型,它会根据传入的水果名称,动态生成对应的响应结构:
type FruitResponse<FruitName extends keyof FruitResources> = { total: number; } & { [K in FruitName]: FruitResources[K] };
这个类型的逻辑是:
- 必须包含
total字段 - 同时包含传入的
FruitName对应的资源字段(比如传入'apples'时,就会包含apples: Array<object>)
3. 改造函数,用泛型绑定参数和响应类型
最后把get函数改成泛型函数,让fruit参数的类型和响应类型关联起来:
const get = async <FruitName extends keyof FruitResources>(fruit: FruitName) => { // 这里TypeScript会根据传入的fruit自动推断响应类型 const data: FruitResponse<FruitName> = await fetchFruit(`https://example.com/${fruit}`); const count = data.total; // 现在TypeScript明确知道data[fruit]是对应的数组类型,不会报错 const applesOrOranges = data[fruit].filter(Boolean); };
为什么之前的联合类型方案不行?
你之前的FruitResponse = ApplesResponse | OrangesResponse是一个联合类型,但TypeScript无法将fruit参数的字面量类型和联合类型的分支关联起来——它只知道data是两种类型中的一种,但不确定具体是哪一种,所以data[fruit]会被推断为never(因为ApplesResponse没有oranges字段,OrangesResponse没有apples字段,交叉后无共同属性),导致类型报错。
而泛型方案通过将fruit参数的类型作为泛型参数,直接绑定到响应类型的字段上,让TypeScript能精准推断出响应的结构,完美实现了类型收窄。
备选方案(不推荐)
如果不想用泛型,也可以通过类型断言临时解决,但这种方式不够优雅,且类型安全度较低:
const get = async (fruit: 'apples' | 'oranges') => { const data: FruitResponse = await fetchFruit(`https://example.com/${fruit}`); const count = data.total; // 手动断言data包含当前fruit对应的字段 const applesOrOranges = (data as { [K in typeof fruit]: Array<object> })[fruit].filter(Boolean); };
内容的提问来源于stack exchange,提问作者spbks
相关产品推荐
相关产品推荐

