三块面板:TS 源码 / 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。收窄是 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
}
typeof === 'string' 分支,编译器把 x 看成 string,length 就合法了——错误发生在「还没收窄」的位置。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 也被收窄)
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()
}