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
相关产品推荐
相关产品推荐

