TS类型守卫机制
# 06.TS类型守卫机制
从联合类型到精确类型——TS 如何在运行时"精确判断"类型?掌握类型守卫,让动态类型安全落地。
# 1. 案例引入
# 1.1 联合类型——编译器"看得见"但"摸不准"
type Shape = Circle | Rectangle | Triangle;
function area(shape: Shape): number {
// shape.radius; // ❌ Shape 上不存在 radius——可能是 Rectangle
// shape.width; // ❌ 同理
// TS 知道你传的是 Shape,但不知道此刻具体是哪个
// 需要你"告诉"编译器:在某个分支里,它是什么
}
# 1.2 类型收窄——联合类型的唯一正解
function area(shape: Shape): number {
if ("radius" in shape) {
// shape 被收窄为 Circle
return Math.PI * shape.radius ** 2;
}
if ("width" in shape) {
// shape 被收窄为 Rectangle
return shape.width * shape.height;
}
// shape 被收窄为 Triangle
return 0.5 * shape.base * shape.height;
}
类型守卫的本质:在运行时告诉编译器"这个分支里,变量的类型已收窄为 X"。
# 2. typeof 守卫——区分基本类型
# 2.1 支持的类型字符串
// typeof 返回的 JS 类型字符串(7种)
// "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
function process(value: string | number | boolean) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // value: string
} else if (typeof value === "number") {
console.log(value.toFixed(2)); // value: number
} else {
console.log(value); // value: boolean
}
}
# 2.2 typeof 的局限性
// typeof null === "object" —— 不会将 null 收窄为 null
function test(x: string | null) {
if (typeof x === "object") {
// x 是 object | null —— 而不是 null!
// x.length; // ❌
}
}
// 正确做法:直接用 === null 或 === undefined
function test2(x: string | null) {
if (x === null) {
console.log("null");
} else {
console.log(x.length); // ✅ x: string
}
}
# 3. instanceof 守卫——判断类的实例
class User {
constructor(public name: string) {}
greet() { return `Hi, ${this.name}`; }
}
class Bot {
constructor(public id: number) {}
respond() { return `Bot #${this.id}`; }
}
function interact(entity: User | Bot) {
if (entity instanceof User) {
entity.greet(); // ✅ entity: User
} else {
entity.respond(); // ✅ entity: Bot
}
}
# 4. in 关键字守卫——判断属性存在
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
side: number;
}
type Shape = Circle | Square;
function area(shape: Shape) {
if ("radius" in shape) {
// shape: Circle —— 因为只有 Circle 有 radius
return Math.PI * shape.radius ** 2;
} else {
// shape: Square
return shape.side ** 2;
}
}
# 5. 自定义类型守卫——is 关键字
# 5.1 基础语法
// 语法:param is Type
// 返回值必须是 boolean
// 判断是否为字符串数组
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
const data: unknown = ["a", "b", "c"];
if (isStringArray(data)) {
// data 被收窄为 string[]
console.log(data.map(s => s.toUpperCase()));
}
# 5.2 自定义守卫的实战价值
// 场景:API 返回的数据结构校验
interface User {
id: number;
name: string;
email: string;
}
// 类型守卫——运行时校验数据是否符合 User 结构
function isUser(obj: unknown): obj is User {
if (typeof obj !== "object" || obj === null) return false;
const u = obj as Record<string, unknown>;
return (
typeof u.id === "number" &&
typeof u.name === "string" &&
typeof u.email === "string"
);
}
// 使用
async function fetchUser(): Promise<unknown> {
const res = await fetch("/api/user");
return res.json();
}
const raw = await fetchUser();
if (isUser(raw)) {
console.log(raw.name); // ✅ raw: User
}
# 6. 等价与赋值收窄
# 6.1 严格等值收窄
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x 和 y 都被收窄为 string(唯一共有类型)
console.log(x.toUpperCase());
}
}
# 6.2 赋值收窄
let x = Math.random() > 0.5 ? "hello" : 42;
// x: string | number
x = "world";
// x: string —— TS 跟踪了赋值的"最新类型"
console.log(x.toUpperCase()); // ✅
# 7. 判别联合(Discriminated Union)
# 7.1 给每个成员一个"类型标签"
// 为联合类型的每个成员添加一个字面量类型的"判别字段"
interface Circle {
kind: "circle"; // ← 判别属性
radius: number;
}
interface Square {
kind: "square"; // ← 判别属性
side: number;
}
interface Triangle {
kind: "triangle"; // ← 判别属性
base: number;
height: number;
}
type Shape = Circle | Square | Triangle;
# 7.2 通过判别属性收窄
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2; // shape: Circle
case "square":
return shape.side ** 2; // shape: Square
case "triangle":
return 0.5 * shape.base * shape.height; // shape: Triangle
}
}
# 7.3 与穷尽性检查结合
// 确保覆盖所有形状
function assertExhaustive(x: never): never {
throw new Error(`未处理的类型: ${x}`);
}
function areaExhaustive(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "triangle":
return 0.5 * shape.base * shape.height;
default:
// 如果将来新增了 Rectangle 但没有添加 case,这里编译报错
return assertExhaustive(shape);
}
}
# 8. never 与穷尽性检查
# 8.1 never 作为"不可能"的守卫
type Result =
| { status: "ok"; data: string }
| { status: "error"; message: string };
function handle(r: Result) {
if (r.status === "ok") {
console.log(r.data);
return;
}
if (r.status === "error") {
console.log(r.message);
return;
}
// 这里 r 的剩余类型是 never
// 当新增一个 status 分支时,这里的 never 就会变成新类型并报错
const exhaustive: never = r;
}
# 9. unknown vs any:最后的防线
# 9.1 any——放弃一切检查
let danger: any = fetchSomeData();
danger.foo(); // ✅ 编译通过,运行时可能崩
danger.property.bar(); // ✅ 编译通过,运行时可能崩
const num: number = danger; // ✅ any 污染一切
# 9.2 unknown——安全的万能类型
let safe: unknown = fetchSomeData();
// safe.foo(); // ❌ 不能直接使用
// safe.property.bar(); // ❌ 不能直接使用
// const num: number = safe; // ❌ 不能赋值
// 必须先验证/收窄
if (typeof safe === "string") {
console.log(safe.toUpperCase()); // ✅
} else if (Array.isArray(safe)) {
console.log(safe.length); // ✅
} else if (isUser(safe)) {
console.log(safe.name); // ✅
}
# 9.3 选型决策
你的场景是什么?
├── 从不可信的 API / 用户输入获取数据 → unknown + 类型守卫
├── 完全放弃类型检查(尽快验证的临时变量)→ any
└── 正常开发 → 永远不用 any 和 unknown
# 10. 实战:结果类型模式(Result Pattern)
// 像 Rust 的 Result 一样处理成功和失败
type Success<T> = { ok: true; data: T };
type Failure = { ok: false; error: string };
type Result<T> = Success<T> | Failure;
// 工厂函数
function ok<T>(data: T): Result<T> {
return { ok: true, data };
}
function fail<T>(error: string): Result<T> {
return { ok: false, error };
}
// 使用判别联合收窄
interface ApiUser { id: number; name: string; }
async function getUser(id: number): Promise<Result<ApiUser>> {
try {
const res = await fetch(`/api/user/${id}`);
if (!res.ok) return fail("网络错误");
const user: ApiUser = await res.json();
return ok(user);
} catch {
return fail("未知错误");
}
}
const result = await getUser(1);
if (result.ok) {
console.log(result.data.name); // ✅ data: ApiUser
} else {
console.error(result.error); // ✅ error: string
}
# 11. 速查表
| 守卫方式 | 语法 | 适用场景 |
|---|---|---|
| typeof | typeof x === "string" | 基本类型(string/number/boolean等) |
| instanceof | x instanceof MyClass | 判断属于哪个类 |
| in | "prop" in x | 判断属性是否存在 |
| 自定义守卫 | x is Type | 复杂结构校验 |
| 严格相等 | x === y | 两个联合类型取交集 |
| 判别属性 | x.kind === "circle" | 联合类型精确定位 |
| 赋值收窄 | x = "hello" | 跟踪最新赋值类型 |
| 穷尽性检查 | never | 确保 switch 全覆盖 |
守卫三原则:
- 能用
typeof不用自定义守卫 - 能用判别联合不用
in关键字 - 外部数据永远用
unknown+is自定义守卫
下一篇:07.TS模块系统详解
上次更新: 2026/06/24, 12:59:24