三块面板:TS 源码 / 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。收窄是 TS 的发动机:你做判断,编译器据此在分支内更新它对变量的看法。

一、typeof 收窄

TS 源码

function len(x: string | number) {
  x.length          // ✗ number 上没有 length
  if (typeof x === 'string') {
    return x.length // ✓ 这个分支 x 被收窄成 string
  }
  return x.toFixed(2)   // ✓ else 分支收窄成 number
}
len.ts:2:5 - error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'. 2 x.length ~~~~~~
为什么报:收窄之前 x 是联合,只能用共有成员。进了 typeof === 'string' 分支,编译器把 x 看成 string,length 就合法了——错误发生在「还没收窄」的位置。

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

[迷你控制台]

二、类型谓词 x is T:把经验交给编译器

function isString(x: unknown): x is string {
  return typeof x === 'string'
}
function use(v: unknown) {
  if (isString(v)) {
    v.toUpperCase()   // ✓ 因为 isString 声明了返回真即 v 是 string
  }
}
const strs = ['a', 1, 'b'].filter(isString)   // strs: string[](filter 也被收窄)

三、in / instanceof 收窄

type Fish = { swim: () => void }
type Bird = { fly: () => void }
function move(a: Fish | Bird) {
  if ('fly' in a) a.fly()     // in 收窄成 Bird
  else a.swim()
}
function when(e: Error | Date) {
  if (e instanceof Error) e.message   // instanceof 收窄(class 运行时真实存在)
  else e.getTime()
}

四、真值收窄的陷阱