TypeScript 中用判别联合类型替代 instanceof 检查

写 TypeScript 的人都遇到过这种代码:函数接受多种形态的对象,分支里用 instanceof'foo' in obj 来判断当前是哪一种。这种写法能跑,但类型推导很多时候不到位,重构时也容易遗漏分支。换成判别联合类型(discriminated union)之后整个世界都清爽了。

一个具体例子

假设我们在写一个 HTTP 客户端,请求结果有三种:成功、业务错误(HTTP 200 但 body 里 code 非 0)、网络错误。早期版本可能长这样:

typescript 复制代码
interface Result {
  data?: unknown
  bizError?: { code: number; message: string }
  networkError?: Error
}

function handle(r: Result) {
  if (r.networkError) {
    console.error('network down', r.networkError)
  } else if (r.bizError) {
    console.warn('biz error', r.bizError.code)
  } else {
    console.log('ok', r.data)
  }
}

问题:

  1. 三个字段都是可选的,调用方能构造出 { data: ..., networkError: ... } 这种自相矛盾的对象
  2. else 分支里 TypeScript 并不能保证 r.data 一定存在
  3. 加一种新的结果类型时,编译器不会提醒所有 handle 函数有遗漏

用判别字段重写

typescript 复制代码
type Result =
  | { kind: 'ok'; data: unknown }
  | { kind: 'bizError'; code: number; message: string }
  | { kind: 'networkError'; cause: Error }

function handle(r: Result) {
  switch (r.kind) {
    case 'ok':
      console.log('ok', r.data)
      return
    case 'bizError':
      console.warn('biz error', r.code)
      return
    case 'networkError':
      console.error('network down', r.cause)
      return
  }
}

每个分支里 TypeScript 自动收窄类型:在 case 'ok' 里访问 r.code 会直接报错。三种状态互斥,不可能同时存在。

加上 exhaustiveness check

更进一步,可以加一个 assertNever 让编译器在你忘记处理新分支时直接报错:

typescript 复制代码
function assertNever(x: never): never {
  throw new Error(`Unhandled variant: ${JSON.stringify(x)}`)
}

function handle(r: Result) {
  switch (r.kind) {
    case 'ok': return console.log('ok', r.data)
    case 'bizError': return console.warn('biz error', r.code)
    case 'networkError': return console.error('network down', r.cause)
    default: return assertNever(r)
  }
}

后面如果在 Result 里加了一个 { kind: 'timeout'; afterMs: number },所有 handle 函数会立刻在 assertNever(r) 那行编译错误,因为 r 的类型不再是 never。这个模式在大型代码库里非常救命,相当于给类型系统挂了一个"穷尽性"的开关。

什么时候不适合

判别联合不是免费的:

  • 如果各个变体之间共享字段很多,用判别字段会有重复
  • 如果变体集合是开放的(比如允许第三方扩展),用 class + 接口可能更合适
  • 跨模块边界传输时(JSON、protobuf)要确认 kind 字段在序列化协议里也能保留

但只要你的"几种状态"是闭集合,判别联合几乎总是比 instanceof 或可选字段更好的选择。

相关推荐
Quz2 分钟前
QML Label 完整用法与 Text 组件选型
前端·qt
妙码生花3 分钟前
从 PHP 到 AI + Golang,程序员自救转型手记(三十四):后台初始化请求实现
前端·javascript·go
天若有情67316 分钟前
前端性能优化:从“按下预加载”到“AI级智能预判系统”
前端·性能优化·js·active·hover·首屏加载
JackieZhengChina27 分钟前
零依赖轻量级JS悬浮提示插件title2tip-js的使用教程
开发语言·javascript·ecmascript
Csvn34 分钟前
📦 CSS Container Queries 实战踩坑:5 个让你怀疑人生的隐藏陷阱
前端
大家的林语冰1 小时前
👍 Rust 为 Node 插上翅膀,反超 Bun 和 pnpm,一体化工具我也有!
前端·javascript·node.js
退休倒计时1 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
坐而论道_1 小时前
兼容低版本 Android:让现代 Web 应用在旧 WebView 上稳定运行
前端·面试
ssshooter1 小时前
Promise 和 async/await 被 try-catch 包裹时的行为差异
前端·javascript·面试
肖志-AI全栈1 小时前
从零讲透 Ajax:XHR、Fetch、JSON 序列化到异步编程全链路
前端·ajax·json