← 返回题目列表

TypeScript 类型收窄有哪些方式?

高频 中等 第 13 / 32 题 更新于 2026/07/27
TypeScript类型收窄类型守卫

简化版

类型收窄是把宽类型缩小到更具体类型。常见方式有 typeofinstanceofin、判别联合、真值判断、自定义类型守卫和控制流分析。

详细版

示例:

function print(value: string | number) {
  if (typeof value === 'string') {
    value.toUpperCase();
  } else {
    value.toFixed();
  }
}

判别联合:

type Result =
  | { type: 'success'; data: string }
  | { type: 'error'; message: string };

function handle(r: Result) {
  if (r.type === 'success') {
    console.log(r.data);
  }
}

自定义类型守卫:

function isString(x: unknown): x is string {
  return typeof x === 'string';
}

完整版教学

一、为什么需要类型收窄

联合类型表示一个值可能是多种类型。使用它之前,TS 要你证明当前到底是哪一种。

这不是 TS 麻烦,而是避免运行时错误。例如 string | number 不能直接调用 toUpperCase,因为 number 没这个方法。

二、typeof 和 instanceof

typeof 适合原始类型:

typeof x === 'boolean'

instanceof 适合类实例:

if (err instanceof Error) {
  console.log(err.message);
}

三、in 操作符

当多个对象类型有不同属性时,可以用 in

if ('swim' in animal) {
  animal.swim();
}

它通过属性存在性缩小类型范围。

四、判别联合是工程最佳实践

给每个分支加一个稳定的字面量字段,比如 typekind,TS 就能非常准确地收窄。

这在接口状态、弹窗类型、图形类型、消息类型中特别常用。相比猜测某个可选属性是否存在,稳定判别字段能让每条控制流分支对应唯一成员。

五、面试追问与工程落地

类型收窄常见追问是“自定义类型守卫是不是一定安全”。类型守卫的返回类型 x is User 是你对编译器的承诺,TS 不会验证函数内部逻辑是否真的完整。如果守卫写错,后面仍可能运行时报错。

还会问可选链是不是类型收窄。user?.name 可以安全访问,但不一定在后续代码里把 user 收窄成非空。真正需要后续复用非空对象时,还是写明确判断:

if (!user) return;
user.name;

工程里建议对接口响应使用运行时 schema 校验,如 Zod、Valibot 或自写 guard,再把 unknown 收窄成业务类型。这样 TypeScript 的静态类型和运行时真实数据才能对齐。

六、理解控制流分析何时保留或撤销收窄

TypeScript 会沿控制流记录赋值、返回、抛错和条件判断,因此提前返回后,后续分支可以自动排除已经处理的类型。收窄并非给变量永久改类型:变量重新赋值、对象属性可能被修改,或者闭包延迟执行时,编译器可能撤销先前结论。稳定的局部常量通常比跨回调依赖可变属性更容易保持安全。

function format(value: string | number | null) {
  if (value === null) return 'empty'
  if (typeof value === 'number') return value.toFixed(2)
  return value.toUpperCase()
}
// 3 个候选经过 null 判断剩 2 个,再经 typeof 各剩 1 个
守卫适合对象关键边界
typeof原始类型、函数typeof null === 'object'
instanceof有真实构造器的实例跨 realm 或伪造原型需谨慎
'key' in obj按属性存在性区分对象可选属性可能出现在两边
真值判断快速排除假值会同时排除 ''0NaN
判别字段业务状态联合字段必须稳定且分支值唯一

如果 string | nullif (value) 收窄,空字符串也会进入 false 分支;这不是编译器错误,而是真值语义本来就包含多个假值。需要只排除空值时,应写 value != null 或分别比较 nullundefined

类型守卫是一份证明:先确认它在运行时检查了哪些事实,再看这些事实是否足够推出声明的目标类型。

七、常见误区与追问

  • 误区:typeof null 的结果是 null。 JavaScript 运行时结果是 'object',所以对象判断还必须排除 null。
  • 误区:真值判断只会排除 null 和 undefined。 它还会排除空字符串、0、NaN、false 等假值,可能误伤合法业务数据。
  • 误区:自定义 x is T 守卫一定可信。 返回类型是开发者对编译器的承诺,函数体检查不完整仍会造成运行时错误。
  • 追问:为什么提前 return 能帮助收窄? 控制流分析知道被排除类型无法到达后续路径,因此从剩余联合中移除它。
  • 追问:in 的 false 分支一定没有该属性吗? 不一定;带可选属性的类型可能同时保留在 true 与 false 两边,需要结合模型设计判断。
  • 追问:重新赋值后为什么类型变宽? 新赋值改变了当前控制流事实,编译器必须按声明类型和新值重新分析。
  • 追问:如何让复杂状态更容易收窄? 使用稳定的字面量判别字段,并在 default 分支做 never 穷尽检查。

八、加强记忆

类型收窄就是“先验明身份,再调用能力”。基础类型用 typeof,类实例用 instanceof,对象属性用 in,复杂状态优先设计判别联合。

再检查守卫是否误排除了合法假值、是否会被重新赋值撤销,收窄结论才经得住边界追问。