typeof、类型守卫与判别联合

本节目标

typeof 类型运算符:从已有的值/变量推出它的类型,避免重复定义。

// 运行环境:Node.js 16+ + typescript;类型检查 npx tsc --noEmit ts-l12.ts

const config = { timeout: 3000, retry: true }
type Config = typeof config // { timeout: number; retry: boolean }
// 以后 config 改了字段,Config 自动跟着变,不用手写两遍

自定义类型守卫:返回值标注成 参数 is 类型,调用后 TS 会在 if 里自动收窄。

interface Dog { kind: 'dog'; bark(): void }
interface Cat { kind: 'cat'; meow(): void }
type Animal = Dog | Cat

// 守卫函数:返回 `v is Dog` 后,if 内 v 自动变 Dog
function isDog(v: Animal): v is Dog {
  return v.kind === 'dog'
}
function speak(a: Animal): void {
  if (isDog(a)) a.bark() // 这里 a 被收窄为 Dog
  else a.meow()          // 这里 a 是 Cat
}

判别联合(Discriminated Union):用一个「共同的可辨识字段」(如 kind)让 TS 精准区分联合成员——上面的 Animal 就是判别联合。kind 叫「判别属性」。

名词解释

类型守卫(Type Guard):一个「返回值是 x is T」的函数。它告诉 TS:「如果我返回 true,那参数就是 T 类型」。调用它进入 if 分支后,TS 立刻把变量收窄成 T,于是你能用 T 的专属方法。它是「替代 as 断言」最安全的手段。

判别联合(Discriminated Union):一组联合类型,每个成员都带一个「相同的、字面量类型的标识字段」(如 kind: 'dog')。有了它,TS 看到 if (x.kind === 'dog') 就能精确定位到对应成员,自动给全部分支正确类型。它是处理「多种形状数据」最优雅的模式。

课后练习

练习 1:写一个 isString(v): v is string 守卫并演示使用。

答案:

function isString(v: unknown): v is string {
  return typeof v === 'string'
}
function len(v: unknown) {
  if (isString(v)) return v.length // 这里 v 是 string
  return 0
}

练习 2:判别联合里「判别属性」必须满足什么条件?

答案:必须是「字面量类型的联合」(如 'dog' | 'cat'),且每个联合成员都有这个同名字段。这样 TS 才能靠比较字面量值精确区分成员。

总结

类型守卫 + 判别联合,是我心中「告别 as 断言」的两把利器。我的观点非常明确:处理联合类型时,永远优先用判别联合,而不是 as 强转。判别联合妙在它把「类型的区分」编码进了「数据的形状」——你写一个 kind 字段,TS 就终身帮你盯着每个分支。自定义守卫则适合「判别属性不好加」的legacy 场景(比如接第三方数据)。这两者共同的目标只有一个:让 TS 在编译期就替你走完所有 if 分支,而不是把这些判断推给运行时去崩。