TypeScript 类型守卫的实现示例
一、什么是类型守卫
类型守卫是 TypeScript 中用于在运行时检查类型,并在编译时相应收窄类型范围的技术。通过类型守卫,联合类型可以被细化为更具体的类型,从而安全地访问特有属性或方法。
function example(value: string | number) {
// value 类型为 string | number
if (typeof value === "string") {
// 这里 value 被收窄为 string
console.log(value.toUpperCase());
} else {
// 这里 value 被收窄为 number
console.log(value.toFixed(2));
}
}
typeof value === "string" 就是一个类型守卫。
二、typeof 类型守卫
2.1 基本用法
typeof 运算符判断 JavaScript 基本类型,支持:"string"、"number"、"boolean"、"symbol"、"bigint"、"undefined"、"object"、"function"。
function process(input: string | number | boolean) {
if (typeof input === "string") {
console.log(input.length); // string
} else if (typeof input === "number") {
console.log(input.toFixed(2)); // number
} else {
console.log(input); // boolean
}
}
2.2 识别 null 和数组的问题
typeof null 返回 "object",typeof [] 也返回 "object",无法区分。
function handle(value: string | null) {
if (typeof value === "object") { // null 也会进入这里
// 这里 value 类型为 null
}
}
对于数组,需要额外判断:
if (Array.isArray(value)) {
// value 收窄为数组
}
2.3 多个类型守卫组合
可以使用 || 和 && 组合条件,TypeScript 能正确收窄。
function fn(value: string | number[] | null) {
if (typeof value === "object" && value !== null) {
// value 收窄为 number[]
}
}
三、instanceof 类型守卫
instanceof 用于判断对象是否是某个类的实例,适用于类继承场景。
class Dog {
bark() { console.log("woof"); }
}
class Cat {
meow() { console.log("meow"); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // 收窄为 Dog
} else {
animal.meow(); // 收窄为 Cat
}
}
instanceof 也能识别内置类型,如 Date、RegExp、Error 等。
function formatDate(value: string | Date) {
if (value instanceof Date) {
return value.toLocaleDateString();
}
return value;
}
注意:instanceof 对字面量类型无效,对接口也无效(接口在运行时不存在)。
四、in 运算符类型守卫
in 用于检查对象是否包含某个属性,常用于区分具有不同属性集的类型。
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
function area(shape: Circle | Square) {
if ("radius" in shape) {
return Math.PI * shape.radius ** 2; // shape 收窄为 Circle
} else {
return shape.sideLength ** 2; // shape 收窄为 Square
}
}
in 守卫比直接判断 kind 字段更通用,但需要确保属性名在类型中唯一。
五、自定义类型守卫(is)
5.1 为什么需要自定义守卫
当内置守卫无法满足复杂判断时(如判断对象是否为特定形状、判断字符串是否为有效邮箱等),可以编写返回类型谓词 parameterName is Type 的函数。
5.2 基本语法
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.length); // value 收窄为 string
}
}
返回类型 value is string 就是类型谓词。函数体返回 boolean,TypeScript 会在 if 条件中根据返回值收窄类型。
5.3 复杂对象守卫
interface User {
name: string;
age: number;
}
function isUser(obj: any): obj is User {
return obj && typeof obj.name === "string" && typeof obj.age === "number";
}
function greet(data: unknown) {
if (isUser(data)) {
console.log(`Hello, ${data.name}`); // data 是 User
}
}
5.4 泛型守卫
类型守卫也可以使用泛型。
function isArrayOf<T>(value: unknown, check: (item: unknown) => item is T): value is T[] {
return Array.isArray(value) && value.every(check);
}
const isNumber = (x: unknown): x is number => typeof x === "number";
const data: unknown = [1, 2, 3];
if (isArrayOf(data, isNumber)) {
// data 类型为 number[]
console.log(data.reduce((a,b)=>a+b,0));
}
5.5 守卫的局限
类型谓词必须使用参数名,不能使用复杂的表达式。一个函数最多只能有一个类型谓词。
// 错误:不能有两个谓词
function isXOrY(value: any): value is X | value is Y { ... }
如果需要判断多种类型,返回联合类型的谓词也是合法的(value is Type1 | Type2)。
六、断言函数(asserts)简介
6.1 基本概念
断言函数是另一种类型守卫,当条件不满足时抛出错误,从而收窄后续代码中的类型。
语法:asserts condition 或 asserts value is Type。
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string");
}
}
function toUpper(value: unknown) {
assertIsString(value);
// 此处 value 类型被收窄为 string
return value.toUpperCase();
}
6.2 与类型守卫的区别
- 类型守卫返回
boolean,用于if分支收窄。 - 断言函数不返回值(或返回
void),如果条件失败则抛出异常,后续代码假设断言成立。
// 类型守卫
if (isString(value)) {
// value 是 string
}
// 断言函数
assertIsString(value);
// value 是 string(无需包裹在 if 里)
6.3 简单断言
也可以直接断言某个条件为真,不限于类型收窄。
function assert(condition: any, msg?: string): asserts condition {
if (!condition) throw new Error(msg ?? "Assertion failed");
}
function process(value: string | null) {
assert(value !== null);
// 此处 value 被收窄为 string(非 null)
}
七、常见错误与注意事项
7.1 typeof 对 null 的判断误区
if (typeof obj === "object") {
// obj 可能是 null 或 {},需要额外排除 null
if (obj !== null) {
// ...
}
}
7.2 instanceof 对原始类型无效
let str = "hello"; console.log(str instanceof String); // false(str 是字符串字面量,不是 String 对象)
使用 typeof 判断原始类型更合适。
7.3 自定义守卫需要严格实现
类型谓词不会自动检查函数体的逻辑是否正确,需要开发者保证返回 true 时参数确实符合声明的类型。
function isFish(pet: any): pet is Fish {
// 错误实现:即使 pet 不是 Fish 也返回 true
return true; // 危险!TypeScript 会信任这个断言
}
7.4 断言函数不能用于箭头函数?
断言函数可以用箭头函数,但返回类型必须写 asserts condition。
const assertString = (value: unknown): asserts value is string => {
if (typeof value !== "string") throw new Error();
};
7.5 守卫顺序与性能
多个守卫可能重复检查,可以按开销从小到大排序。
八、综合示例
// 定义几种数据类型
interface Car {
wheels: number;
drive(): void;
}
interface Boat {
sails: number;
sail(): void;
}
interface Plane {
wings: number;
fly(): void;
}
type Vehicle = Car | Boat | Plane;
// 自定义类型守卫
function isCar(vehicle: Vehicle): vehicle is Car {
return "drive" in vehicle;
}
function isBoat(vehicle: Vehicle): vehicle is Boat {
return "sail" in vehicle;
}
function isPlane(vehicle: Vehicle): vehicle is Plane {
return "fly" in vehicle;
}
// 使用守卫
function operate(vehicle: Vehicle) {
if (isCar(vehicle)) {
vehicle.drive();
} else if (isBoat(vehicle)) {
vehicle.sail();
} else if (isPlane(vehicle)) {
vehicle.fly();
} else {
const _exhaustive: never = vehicle;
}
}
// 断言函数示例:确保值非空且为数字
function assertNumber(value: unknown): asserts value is number {
if (typeof value !== "number") {
throw new TypeError("Expected a number");
}
}
function increment(value: unknown) {
assertNumber(value);
return value + 1; // value 已收窄为 number
}
// 复杂对象守卫
interface ApiResponse {
status: number;
data?: unknown;
}
function isSuccessResponse(resp: ApiResponse): resp is ApiResponse & { status: 200; data: object } {
return resp.status === 200 && resp.data !== null && typeof resp.data === "object";
}
const response: ApiResponse = { status: 200, data: { id: 1 } };
if (isSuccessResponse(response)) {
console.log(response.data.id); // 安全访问
}
九、小结
| 守卫类型 | 语法示例 | 适用场景 |
|---|---|---|
| typeof | typeof x === "string" | 基本类型判断 |
| instanceof | x instanceof Date | 类实例判断 |
| in | "prop" in obj | 对象属性存在判断 |
| 自定义 is | function isT(x: any): x is T { ... } | 复杂类型判断、接口形状判断 |
| 断言 asserts | function assertIsT(x: any): asserts x is T | 失败抛异常,后续代码自动收窄 |
到此这篇关于TypeScript 类型守卫的实现示例的文章就介绍到这了,更多相关TypeScript 类型守卫内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论