讲解

联合类型只有「共有能力」,想用某个成员的特有方法,必须先告诉编译器「此刻它是哪个成员」——这个过程叫类型收窄(narrowing)。TypeScript 的控制流分析会跟踪每一处判断,在分支内部自动把变量收窄到对应类型,这是整个类型系统最符合直觉、也最强大的部分。

常用的收窄手段有五种。typeof 区分原始类型(typeof x === "string");instanceof 区分类的实例;in 运算符判断对象是否拥有某属性("role" in user);等值判断直接区分字面量(status === "paid")以及 null/undefined(value != null 同时排除两者);真值判断排除 undefined、null、空串、0 等假值(注意 0 和空串也是假值,容易误伤)。

最工程化的一种是判别联合(discriminated union):给联合的每个成员加一个字面量类型的公共字段(判别字段,习惯叫 kind/type),switch 这个字段即可精确收窄,还能配合 never 做穷举检查——新增成员时漏处理的分支会直接编译报错。这是处理 API 返回、消息事件、状态机的标准姿势。

示例

typeof 与真值收窄:

function toLines(input: string | string[] | undefined): string[] {
  if (input === undefined) {
    return [];
  }
  if (typeof input === "string") {
    return input.split("\n"); // 此分支 input 是 string
  }
  return input; // 剩下的只可能是 string[]
}

console.log(toLines("第一行\n第二行"));
console.log(toLines(["a", "b"]));
console.log(toLines(undefined));

instanceof 与 in 收窄:

function describeError(err: Error | string): string {
  if (err instanceof Error) {
    return `${err.name}: ${err.message}`;
  }
  return err;
}

type Cat = { kind: "cat"; meow(): string };
type Dog = { kind: "dog"; bark(): string };

function speak(pet: Cat | Dog): string {
  if ("meow" in pet) {
    return pet.meow();
  }
  return pet.bark();
}

console.log(describeError(new TypeError("类型不符")));
console.log(speak({ kind: "dog", bark: () => "汪" }));

判别联合 + 穷举检查(推荐模式):

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rect":
      return shape.width * shape.height;
    default: {
      const exhaustive: never = shape; // 新增成员漏处理时这里报错
      return exhaustive;
    }
  }
}

console.log(area({ kind: "circle", radius: 2 }).toFixed(2));
console.log(area({ kind: "rect", width: 3, height: 4 }));

常见坑

  • 用真值判断误伤 0 和空串:if (value) 会把 0、"" 也当不存在;判空用 value == null 或 === undefined,语义才准确。
  • 在异步回调里收窄失效:收窄只沿控制流有效,setTimeout 回调里对 let 变量的收窄可能被编译器作废(变量可能被别处重新赋值),必要时先存到 const。
  • 判别字段用了 string:判别联合的判别字段必须是字面量类型,写成 kind: string 整个判别机制就失效了。
  • instanceof 跨运行时失效:iframe、多包重复安装的类,instanceof 判断为假;库代码里优先用判别字段或结构化检查。

小结

收窄 = 让编译器在分支内认清成员:typeof、instanceof、in、等值、真值五种手段;判别联合 + switch + never 穷举是业务建模的黄金模式。下一章讲枚举,并与字面量联合做取舍对比。