三块面板:TS 源码 / 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。看联合的「取共有成员」、可辨识联合的 switch 收窄。

一、联合类型只用得到「所有分支共有」的成员

TS 源码

type ID = string | number
function render(id: ID) {
  id.toString()      // ✓ string/number 都有
  id.length          // ✗ number 上没有 length
  id.toUpperCase()   // ✗ 未收窄前不能用某一分支独有的成员
}
render.ts:4:6 - error TS2339: Property 'length' does not exist on type 'ID'. Property 'length' does not exist on type 'number'. render.ts:5:6 - error TS2339: Property 'toUpperCase' does not exist on type 'ID'. Property 'toUpperCase' does not exist on type 'number'.
为什么报:联合类型在收窄前,成员是各分支的交集。length/toUpperCase 只在 string 上有,编译器不能保证 id 就是 string,所以拦下。

二、可辨识联合:switch 自动把每支收窄

等价 JS(去掉类型后可真跑)

擦除后 switch(s.kind) 照常按标签分派——TS 的收窄读的就是这个标签字段。点不同形状看面积。

[迷你控制台]

TS 源码

type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'rect'; w: number; h: number }
  | { kind: 'triangle'; base: number; height: number }

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle':   return Math.PI * s.r ** 2      // s 收窄成 circle,s.r 合法
    case 'rect':     return s.w * s.h               // 只能访问 w/h
    case 'triangle': return (s.base * s.height) / 2
    default:
      const _never: never = s   // 处理全后 s 应为 never;漏支就在这里报错
      return _never
  }
}

三、交叉类型:字段叠加

type HasName = { name: string }
type HasAge  = { age: number }
type Person  = HasName & HasAge   // { name: string; age: number }
const p: Person = { name: '阿蓝', age: 18 }   // 两边字段都要给