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

如何使用泛型与条件类型缩小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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:33:08