讲解
联合类型只有「共有能力」,想用某个成员的特有方法,必须先告诉编译器「此刻它是哪个成员」——这个过程叫类型收窄(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 穷举是业务建模的黄金模式。下一章讲枚举,并与字面量联合做取舍对比。