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

TypeScript如何基于字面量属性值窄化联合类型

问题根源

你最初的写法无法生效,核心原因有两个:

  • TypeScript的Exclude工具类型仅能处理联合类型,无法从原始类型number中排除单个字面量200,Exclude<number, 200>的计算结果就是number本身,TypeScript目前不存在“非200的数字”这种内置类型。
  • 可辨识联合的类型收窄要求所有分支的判别属性(这里是status)类型无重叠。你定义的联合中,错误分支的status: number完全包含了成功分支的status: 200,TypeScript的控制流分析会认为两个分支都可能出现status === 200的情况,自然无法联动缩小body的类型。

TypeScript目前尚未落地否定类型(即not 200这类类型)特性,无法直接表示“除了200之外的所有数字”,但可以通过两种无额外成本的方案实现你要的推断效果。

推荐方案:保留status字面量类型(零运行时开销)

实际业务场景中,所有响应对象都是在请求封装层统一创建的,你只需要在创建响应时通过as const保留status的字面量类型,不需要修改ResponseType的基础定义,TypeScript就能自动完成类型收窄:

// 类型定义保持和你最初的写法逻辑一致
type ResponseType<T> = 
  | { status: 200; body: T }
  | { status: number; body: undefined };

// 请求封装函数示例,返回时加as const保留字面量类型
async function request<T>(url: string): Promise<ResponseType<T>> {
  const res = await fetch(url);
  if (res.status === 200) {
    const body = await res.json() as T;
    // 这里as const会把status的类型推断为字面量200,而不是宽泛的number
    return { status: 200, body } as const;
  }
  // 这里res.status本身就是具体的数字字面量(比如404/500),加as const后会保留具体类型
  return { status: res.status, body: undefined } as const;
}

使用时完全不需要额外的类型守卫,直接判断status === 200就能得到正确的类型推断:

function doSomething<T extends {property: "value"}>(r: ResponseType<T>): void {
    if (r.status === 200) {
        // 此处r.body自动推断为T类型,可以正常访问property属性
        console.log(r.body.property);
    } else {
        // 此处r.body自动推断为undefined
        console.log("no body");
    }
}

如果是手动构造响应对象,只要加as const配合satisfies做类型检查,也能得到一样的收窄效果:

const successResp = { status: 200, body: { property: "value" } } as const satisfies ResponseType<{property: "value"}>;
const failResp = { status: 500, body: undefined } as const satisfies ResponseType<{property: "value"}>;
备选方案:通用类型守卫(兼容宽泛status:number场景)

如果你需要兼容手动传入status: number类型对象的场景,可以定义一个通用的类型守卫函数,运行时仅做一次相等判断,无额外开销,调用方不需要自己编写类型守卫逻辑:

function isSuccess<T>(resp: ResponseType<T>): resp is { status: 200; body: T } {
  return resp.status === 200;
}

使用时直接调用该函数即可完成收窄:

function doSomething<T extends {property: "value"}>(r: ResponseType<T>): void {
    if (isSuccess(r)) {
        console.log(r.body.property);
    } else {
        console.log("no body");
    }
}

注意:如果直接给status标注宽泛的number类型再强转为ResponseType,属于不安全的类型断言,本身就会绕过TypeScript的类型检查,不在常规业务场景的考虑范围内。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 10:48:44