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

TypeScript联合类型自定义类型守卫类型推断异常求助

问题:自定义类型守卫无法正确推断Axios响应的data类型

我想用联合类型定义Axios的成功/错误响应,通过自定义类型守卫根据状态码推断数据类型。IntRange是一个生成从n到m整数联合类型的工具类型(比如IntRange<200,300>等价于200|201|...|299)。

当前代码如下:

type SuccessCode = IntRange<200, 300>
type ErrorCode = IntRange<0, 200> | IntRange<300, 600>

interface SuccessResponse<T> extends Omit<AxiosResponse<T>, 'status'> {
   status: SuccessCode
}

interface ErrorResponse extends Omit<AxiosResponse<{"message": string}>, 'status'> {
   status: ErrorCode
}

type ResType<T> = SuccessResponse<T> | ErrorResponse

function isSuccessStatus(status: number): status is SuccessCode {
  return Number.isInteger(status) && (status >= 200 && status < 300)
}

现在遇到的问题:

  • 直接用if (status === 204)时,data类型能正确推断为T;
  • 但调用if (isSuccessStatus(status))时,虽然控制台返回true,但data类型被推断为T | {"message": string},而非预期的T;
  • 即使直接枚举status === 200 || status === 201 || ...能让status类型被推断为SuccessCode,data类型依然是T | {"message": string}。

请问我的代码哪里出了问题?


问题原因与解决方案

核心问题

你的类型守卫只针对单独的status字段做类型窄化,但TypeScript无法将这个单独字段的类型推断关联回整个ResType<T>联合类型。联合类型的自动窄化需要直接检查联合类型实例的判别式属性,而不是单独提取出来的属性值。

简单来说:你告诉TypeScriptstatus是SuccessCode,但它没办法自动把这个信息映射到“整个响应对象是SuccessResponse<T>”这个结论上——因为联合类型的关联关系需要通过守卫整个对象来建立。

修正方案

把类型守卫改成接收整个响应对象,直接判断并断言它是SuccessResponse<T>:

type SuccessCode = IntRange<200, 300>
type ErrorCode = IntRange<0, 200> | IntRange<300, 600>

interface SuccessResponse<T> extends Omit<AxiosResponse<T>, 'status'> {
   status: SuccessCode
}

interface ErrorResponse extends Omit<AxiosResponse<{"message": string}>, 'status'> {
   status: ErrorCode
}

type ResType<T> = SuccessResponse<T> | ErrorResponse

// 修改类型守卫,接收整个响应对象
function isSuccessResponse<T>(res: ResType<T>): res is SuccessResponse<T> {
  return Number.isInteger(res.status) && res.status >= 200 && res.status < 300
}

使用示例

现在在判断时直接传入整个响应对象,TypeScript就能正确窄化data的类型:

function handleResponse<T>(res: ResType<T>) {
  if (isSuccessResponse(res)) {
    // 这里res.data的类型自动推断为T
    console.log(res.data)
  } else {
    // 这里res.data的类型自动推断为{"message": string}
    console.error(res.data.message)
  }
}

为什么直接枚举status有效?

当你写if (status === 200 || status === 201 || ...)时,TypeScript会把这个条件视为对联合类型ResType<T>的判别式检查——因为status是联合类型的共同属性,且你直接枚举了SuccessCode的具体值,TypeScript能直接关联到对应的联合分支。但这种方式扩展性差,而修改后的类型守卫既保持了灵活性,又能正确触发类型窄化。

内容的提问来源于stack exchange,提问作者erin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 11:30:57