类型守卫的实现方式
类型守卫的实现方式
一句话概括
类型守卫就是你写的 if 判断,TypeScript 编译器能”看懂”——typeof x === "string" 之后,x 在 if 块里就是 string,不用手动 as,也不用担心运行时炸。
核心知识点
1. typeof 守卫——基本类型判断
1
2
3
4
5
6
7
8
function format(x: string | number) {
if (typeof x === "string") {
return x.toUpperCase(); // x: string ✅
}
return x.toFixed(2); // x: number ✅
}
// 能收窄的类型:string | number | boolean | symbol | undefined | function | bigint
// ⚠️ typeof null === "object",所以 typeof 不能区分 null 和对象
2. instanceof 守卫——类/构造函数判断
1
2
3
4
5
6
7
8
9
10
11
class Dog { bark() {} }
class Cat { meow() {} }
function handle(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // animal: Dog ✅
} else {
animal.meow(); // animal: Cat ✅
}
}
// 本质是检查原型链:new.target.prototype 是否在实例的原型链上
3. in 守卫——属性存在性判断
1
2
3
4
5
6
7
8
9
10
11
interface User { name: string; email: string; }
interface Admin { name: string; permissions: string[]; }
function greet(p: User | Admin) {
if ("email" in p) {
console.log(p.email); // p: User ✅
} else {
console.log(p.permissions); // p: Admin ✅
}
}
// 当你不需要创建类、只想通过"有无某属性"区分类型时,in 最方便
4. 自定义类型谓词 is——最灵活的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Fish { swim(): void; }
interface Bird { fly(): void; }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // pet: Fish ✅
} else {
pet.fly(); // pet: Bird ✅
}
}
类型谓词的超级实用场景——数组过滤:
1
2
3
4
5
6
7
8
9
10
11
// ❌ 不用类型谓词:filter 后仍是原类型
const arr = [1, null, 2, undefined, 3];
const filtered = arr.filter(x => x != null);
// filtered: (number | null | undefined)[] ← 类型没变!
// ✅ 用类型谓词:filter 后类型收窄
function isNonNull<T>(x: T | null | undefined): x is T {
return x != null;
}
const safe = arr.filter(isNonNull);
// safe: number[] ← 收窄了!
5. 可辨识联合——最优雅的模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Result =
| { kind: "ok"; data: unknown }
| { kind: "err"; message: string; code: number }
| { kind: "loading" };
function handle(r: Result) {
switch (r.kind) {
case "ok": return r.data; // r 收窄到 ok 分支 ✅
case "err": return r.message; // r 收窄到 err 分支 ✅
case "loading": return "wait…";
default: {
const _exhaustive: never = r; // 穷尽检查:漏了分支会报错
return _exhaustive;
}
}
}
// kind 就是"判别属性"(discriminant),每个分支有唯一字面量值
「其实你每天都在用」
- API 响应处理:
if (res.code === 200)后 data 字段类型自动确定 - null 检查:
if (user)之后 user 就不是 null 了(严格模式下) - filter(Boolean):配合类型谓词可以写
arr.filter((x): x is NonNullable<T> => x != null) - 表单校验:不同字段类型有不同的值类型,switch 分支自动收窄
- React props 类型:
if ("children" in props)判断是否传入 children
常见误解(FAQ)
❌ 误区 1:类型守卫会在运行时执行类型检查
类型守卫的语法是编译时作用——它告诉 TS 编译器”这个 if 分支里变量是什么类型”。运行时检查的是你写在守卫函数体里的逻辑(如 typeof x === "string" 或 "swim" in pet),而不是类型注解。
❌ 误区 2:instanceof 能检查 interface
不能。instanceof 依赖原型链,interface 是纯类型概念,编译后不存在。要对 interface 做类型守卫,用 in 或自定义类型谓词。
❌ 误区 3:写了 is 类型谓词就不用管函数体对不对了
类型谓词的返回类型声明只是”承诺”——如果函数体逻辑有 bug,TS 不会帮你验证。pet is Fish 如果实际判断逻辑写错了,运行时照样炸。
❌ 误区 4:if (typeof x === "object") 能判断对象
不能区分普通对象和 null,因为 typeof null === "object" 是 JS 的著名遗留 bug。正确写法:if (x !== null && typeof x === "object")。
一句话总结
类型守卫的本质是让 TS 编译器”读懂你的 if”——用好它,90% 的 as 断言都可以被消灭,代码安全性和可读性双提升。
本文由作者按照 CC BY 4.0 进行授权