如何使用泛型与条件类型缩小API响应类型?
问题:根据API响应的productType实现类型缩小失败
我向一个API端点发送请求,其响应会根据返回结果中的productType字段有所不同,示例响应如下:
// response 1 { "productType": "Product1", "product": "Some product information" } // response 2 { "productType": "Product2", "product": {"moreNestedFields": "Some product information"} }
我希望在客户端代码中根据productType执行不同逻辑(缩小类型),但尝试的代码没有达到预期效果:
type Product = 'Product1' | 'Product2'; type MyResponse<TProduct extends Product> = { product: TProduct extends 'Product1' ? 'SomeType' : 'SomeOtherType'; productType: TProduct; }; function getResponse<T extends Product>() { // 实际是fetch请求 return {} as MyResponse<T>; } const response = getResponse(); if (response.productType === 'Product1') { // 预期myProduct类型为'SomeType',但实际是'SomeType' | 'SomeOtherType' const myProduct = response.product; // 处理myProduct的逻辑 } else { // .. }
我遗漏了什么?该如何实现正确的类型缩小?
解决方案
问题出在你使用泛型条件类型的方式上:当你调用getResponse()时,由于没有指定具体的类型参数,TypeScript会将T推断为Product1 | Product2,此时MyResponse<T>会被解析为product字段是'SomeType' | 'SomeOtherType'的联合类型,而且TypeScript无法将productType的字面量判断和product的类型关联起来——因为泛型条件类型在这里已经被展开成了扁平的联合,不是可区分联合类型(Discriminated Union)。
正确的做法是直接定义可区分联合类型,让TypeScript能通过productType这个区分字段自动缩小类型:
1. 重新定义响应类型
// 分别定义每种产品的响应结构 type Product1Response = { productType: 'Product1'; product: 'SomeType'; // 对应实际的字符串类型 }; type Product2Response = { productType: 'Product2'; product: { moreNestedFields: string }; // 对应实际的嵌套对象类型 }; // 组合成联合类型 type MyResponse = Product1Response | Product2Response;
2. 修改请求函数
不需要泛型,直接返回联合类型(如果是异步请求就返回Promise):
function getResponse(): Promise<MyResponse> { // 替换为实际的fetch逻辑 return fetch('/your-api-endpoint') .then(res => res.json()) .then(data => data as MyResponse); }
3. 使用类型缩小
此时TypeScript会自动根据productType的判断缩小product的类型:
async function handleResponse() { const response = await getResponse(); if (response.productType === 'Product1') { // myProduct类型自动推断为'SomeType' const myProduct = response.product; // 执行Product1相关逻辑 } else { // myProduct类型自动推断为{ moreNestedFields: string } const myProduct = response.product; // 执行Product2相关逻辑 } }
可选:保留泛型的替代方案
如果一定要保留泛型写法,可以通过自定义类型守卫来实现类型缩小,但这种写法不如可区分联合简洁:
// 原类型定义不变 type Product = 'Product1' | 'Product2'; type MyResponse<TProduct extends Product> = { product: TProduct extends 'Product1' ? 'SomeType' : 'SomeOtherType'; productType: TProduct; }; function getResponse<T extends Product>() { return {} as MyResponse<T>; } // 自定义类型守卫 function isProduct1Response(response: MyResponse<Product>): response is MyResponse<'Product1'> { return response.productType === 'Product1'; } // 使用类型守卫 const response = getResponse(); if (isProduct1Response(response)) { // myProduct类型为'SomeType' const myProduct = response.product; } else { // myProduct类型为'SomeOtherType' const myProduct = response.product; }
推荐优先使用可区分联合类型的方案,它更符合TypeScript的类型系统设计,代码也更直观。
内容的提问来源于stack exchange,提问作者Cagri Uysal
相关产品推荐
相关产品推荐

