Skip to content

类型缩小(Type Narrowing)

什么是「类型缩小」?

TypeScript 的控制流分析会在当前作用域内根据条件判断把某个值的类型从宽(例如 T | undefined)缩小为更具体的类型(例如 T)。但这种缩小只在当前的控制流/函数作用域内可靠生效,跨函数、跨 await/异步边界或闭包时通常不起作用。

为什么会失效

  • 函数边界:在 if 块外定义/调用的函数无法“记住”外层的缩小状态。
  • await/异步:await 表明中间可能会有外部变更,编译器不会继续假设此前的缩小成立。
  • 可变变量(let/var):非 const 的局部变量可能被修改,缩小不稳定。
  • 闭包/回调:编译器不能保证回调执行时变量仍然满足之前的条件。

「类型缩小」示例

错误示例(不可依赖跨函数缩小):

ts
type Props = { obj?: { name: string; id: number } }
const props: Props = {}

if (props.obj) {
  foo()
}

function foo() {
  console.log(props.obj.name) // Error: 可能为 undefined
}

await 导致缩小失效:

ts
type Props = { obj?: { name: string; id: number } }
const props: Props = {}

if (props.obj) {
  await someAsync()
  console.log(props.obj.name) // Error: 可能为 undefined
}

解决方案(按推荐顺序)

  1. 局部 const 拷贝并传参(推荐)
ts
if (props.obj) {
  const obj = props.obj // 局部 const 已被缩小
  foo(obj)
}

function foo(o: { name: string; id: number }) {
  console.log(o.name)
}
  1. 早返回(在同一函数内)
ts
function doIt() {
  if (!props.obj) return
  // 这里 props.obj 已缩小
  console.log(props.obj.name) // OK
}
  1. 用户自定义类型守卫(type guard)
ts
function hasObj(p: Props): p is Props & { obj: { name: string; id: number } } {
  return !!p.obj
}

if (hasObj(props)) {
  console.log(props.obj.name) // 在 if 块内被缩小
}
  1. 断言函数(asserts) — 在通过校验后,对后续代码生效
ts
function assertHasObj(p: Props): asserts p is Props & { obj: { name: string } } {
  if (!p.obj) throw new Error('missing obj')
}

assertHasObj(props)
console.log(props.obj.name) // 在 assert 调用之后被视为存在
  1. 可选链(安全读取,返回 undefined)
ts
console.log(props.obj?.name) // 如果 obj 为 undefined,结果为 undefined
  1. 非空断言(!)——不安全但简便
ts
console.log(props.obj!.name) // 告诉编译器“我确定存在”,若实际为 undefined 会抛运行时错误
  1. 把值复制到局部并在 await 之后使用
ts
if (props.obj) {
  const obj = props.obj
  await someAsync()
  console.log(obj.name) // 安全:使用的是之前的局部常量
}

其它有用的缩小手段

  • typeof / instanceof / in
ts
if (typeof x === 'string') {
  /* x: string */
}
if (obj instanceof Date) {
  /* obj: Date */
}
if ('prop' in obj) {
  /* obj.prop 存在 */
}
  • 判别联合(discriminated unions)
ts
type A = { kind: 'a'; foo: string }
type B = { kind: 'b'; bar: number }
type U = A | B

function f(u: U) {
  if (u.kind === 'a') {
    console.log(u.foo) // u 被缩小为 A
  }
}

基于 MIT 许可发布