类型守卫的实现方式深度解析
深入解析TypeScript类型守卫的typeof、instanceof、in守卫及自定义类型谓词is的实现方式
一句话概括
类型守卫是TypeScript中用于在运行时精准缩小联合类型范围的核心机制,通过typeof、instanceof、in和自定义类型谓词is实现编译时的类型安全窄化。
背景
在TypeScript开发中,我们经常遇到联合类型(Union Types)的场景。例如,一个函数可能接受string | number类型的参数,但在实际处理逻辑中,我们需要针对不同类型执行不同的操作。这时就会产生一个问题:TypeScript在编译时如何确定联合类型的具体类型?
1
2
3
4
function processValue(value: string | number) {
// 错误:无法直接调用字符串方法
console.log(value.toUpperCase()); // ❌ 编译错误
}
传统解决方案是使用类型断言(Type Assertion):
1
2
3
4
5
function processValue(value: string | number) {
if (typeof value === 'string') {
console.log((value as string).toUpperCase()); // ✅ 但冗长且不安全
}
}
这种方式存在两个问题:
- 冗余代码:每次使用都需要重复类型断言
- 安全隐患:类型断言不会在运行时验证,可能隐藏错误
类型守卫(Type Guards) 应运而生,它能够在条件判断中自动缩窄类型范围,让TypeScript编译器”智能”地识别出当前作用域中的具体类型。
在面试中,类型守卫是TypeScript高级特性的必考点,直接考察候选人对类型系统原理的理解深度。
系列衔接:上一篇我们深入探讨了《映射类型的原理》,理解了如何通过映射类型实现类型的批量转换;本篇将聚焦类型守卫,解决联合类型的运行时类型识别问题;下一篇《infer关键字深入》将进一步探索类型推断的高级应用。
概念与定义
什么是类型守卫?
类型守卫(Type Guard) 是一种表达式,当在条件判断中使用时,能够将联合类型窄化(Narrowing)到更具体的类型。
核心作用:
- 类型窄化:将宽类型(如
string | number)缩窄为具体类型(如string) - 编译时安全:在编译阶段提供类型检查,避免运行时错误
- 代码简洁:消除冗余的类型断言,提升代码可读性
工作原理: 类型守卫通过控制流分析(Control Flow Analysis)实现。当TypeScript编译器遇到类型守卫表达式时,会在条件为true的分支中自动更新变量的类型信息。
1
2
3
4
5
6
7
8
9
10
// 类型窄化的直观示例
function example(value: string | number) {
if (typeof value === 'string') {
// 此作用域内,value 被窄化为 string 类型
value.toUpperCase(); // ✅ 类型安全
} else {
// 此作用域内,value 被窄化为 number 类型
value.toFixed(2); // ✅ 类型安全
}
}
最小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
// 最小可运行示例:typeof 类型守卫
function formatValue(value: string | number): string {
if (typeof value === 'string') {
// typeof 守卫:value 在此作用域被窄化为 string
return value.toUpperCase();
} else {
// 自动推断:value 在此作用域被窄化为 number
return value.toFixed(2);
}
}
console.log(formatValue("hello")); // 输出: HELLO
console.log(formatValue(3.14159)); // 输出: 3.14
核心知识点拆解
1. typeof 守卫
typeof 守卫用于判断基本数据类型(string、number、boolean、symbol、undefined、function)。
语法:
1
2
3
if (typeof variable === 'typeString') {
// variable 在此作用域被窄化为对应类型
}
支持的类型字符串:
'string''number''boolean''symbol''undefined''function''object'(注意:null也会返回'object')
示例:
1
2
3
4
5
6
7
8
9
10
11
12
function processInput(input: string | number | boolean) {
if (typeof input === 'string') {
// input: string
console.log(input.length);
} else if (typeof input === 'number') {
// input: number
console.log(input.toFixed(2));
} else {
// input: boolean
console.log(input ? 'true' : 'false');
}
}
局限性:
- 无法区分自定义对象类型(如
typeof obj总是返回'object') - 无法检测
null(会错误返回'object')
1
2
3
4
5
6
function handleValue(value: string | null) {
if (typeof value === 'object') {
// ⚠️ 危险:null 也会进入此分支
console.log(value.toUpperCase()); // ❌ 运行时错误
}
}
2. instanceof 守卫
instanceof 守卫用于检查对象是否是特定类的实例,适用于自定义类、内置对象(如 Date、RegExp、Array 等)。
语法:
1
2
3
if (variable instanceof ClassName) {
// variable 在此作用域被窄化为 ClassName 类型
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
move() { console.log('Moving...'); }
}
class Dog extends Animal {
bark() { console.log('Woof!'); }
}
class Cat extends Animal {
meow() { console.log('Meow!'); }
}
function handleAnimal(animal: Animal) {
if (animal instanceof Dog) {
// animal: Dog
animal.bark();
} else if (animal instanceof Cat) {
// animal: Cat
animal.meow();
} else {
// animal: Animal
animal.move();
}
}
高级用法:自定义类与接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 接口无法直接用 instanceof 检查,需要类实现
interface Shape {
area(): number;
}
class Circle implements Shape {
constructor(public radius: number) {}
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
area() { return this.width * this.height; }
}
function printArea(shape: Shape) {
if (shape instanceof Circle) {
console.log(`圆形面积: ${shape.area()}, 半径: ${shape.radius}`);
} else if (shape instanceof Rectangle) {
console.log(`矩形面积: ${shape.area()}, 宽: ${shape.width}, 高: ${shape.height}`);
}
}
注意事项:
instanceof检查原型链,对于复杂继承关系可能产生意外结果- 无法用于检查基本类型(
string、number等)
3. in 守卫
in 守卫用于检查对象是否包含特定属性,适用于区分具有不同属性签名的对象类型。
语法:
1
2
3
if ('propertyName' in variable) {
// variable 在此作用域被窄化为包含该属性的类型
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface User {
id: number;
name: string;
email: string;
}
interface Admin {
id: number;
name: string;
permissions: string[];
}
function handlePerson(person: User | Admin) {
if ('email' in person) {
// person: User
console.log(`用户邮箱: ${person.email}`);
} else if ('permissions' in person) {
// person: Admin
console.log(`管理员权限: ${person.permissions.join(', ')}`);
}
}
高级用法:嵌套属性检查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface ApiResponseSuccess {
status: 'success';
data: any;
}
interface ApiResponseError {
status: 'error';
message: string;
code: number;
}
function handleResponse(response: ApiResponseSuccess | ApiResponseError) {
if ('data' in response) {
// response: ApiResponseSuccess
console.log('请求成功:', response.data);
} else {
// response: ApiResponseError
console.log(`请求失败 [${response.code}]: ${response.message}`);
}
}
技巧:结合属性存在性进行多重检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getSmallPet(pet: Bird | Fish) {
if ('fly' in pet) {
// pet: Bird
pet.fly();
} else {
// pet: Fish
pet.swim();
}
// 共同属性无需类型守卫
pet.layEggs();
}
4. 自定义类型谓词(Type Predicate)— is 关键字
当内置类型守卫无法满足需求时,可以使用自定义类型谓词(Type Predicate)创建灵活的类型守卫函数。
语法:
1
2
3
function isTypeName(variable: any): variable is TypeName {
// 返回 boolean 的逻辑判断
}
核心要点:
- 返回类型必须是
parameterName is Type形式 - 函数体返回
boolean值 - TypeScript 会在条件为
true时窄化类型
基础示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
// 自定义类型守卫函数
function isFish(pet: Bird | Fish): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Bird | Fish) {
if (isFish(pet)) {
// pet: Fish
pet.swim();
} else {
// pet: Bird
pet.fly();
}
}
高级应用:复杂对象验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface UserProfile {
id: number;
name: string;
email: string;
age?: number;
}
// 运行时类型验证 + 类型守卫
function isUserProfile(obj: any): obj is UserProfile {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string' &&
(obj.age === undefined || typeof obj.age === 'number')
);
}
function processProfile(data: any) {
if (isUserProfile(data)) {
// data: UserProfile
console.log(`用户: ${data.name}, 邮箱: ${data.email}`);
} else {
console.error('无效的用户数据');
}
}
泛型类型谓词:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 通用数组类型检查
function isArrayOf<T>(
arr: any,
checkFn: (item: any) => item is T
): arr is T[] {
return Array.isArray(arr) && arr.every(item => checkFn(item));
}
// 使用示例
const mixedData: any[] = [1, 2, 3, 4];
if (isArrayOf(mixedData, (item): item is number => typeof item === 'number')) {
// mixedData: number[]
const sum = mixedData.reduce((a, b) => a + b, 0);
console.log(`数字总和: ${sum}`);
}
5. 区分类型守卫与类型断言(as)
类型断言(Type Assertion):
1
2
3
4
5
function processValue(value: string | number) {
// 强制告诉编译器值的类型,无运行时检查
const str = value as string; // ⚠️ 危险:如果 value 是 number,运行时可能出错
console.log(str.toUpperCase());
}
类型守卫(Type Guard):
1
2
3
4
5
6
7
function processValue(value: string | number) {
// 通过条件判断,编译器自动窄化类型
if (typeof value === 'string') {
// 安全:value 已被确认为 string
console.log(value.toUpperCase());
}
}
核心区别对比表:
| 特性 | 类型守卫 | 类型断言 |
|---|---|---|
| 运行时检查 | ✅ 有 | ❌ 无 |
| 安全性 | ✅ 编译时+运行时安全 | ⚠️ 仅编译时,可能运行时错误 |
| 代码冗余 | ✅ 无冗余 | ❌ 每次使用需重复断言 |
| 适用场景 | 条件分支中的类型窄化 | 确定类型但编译器无法推断时 |
| 性能 | ✅ 有运行时开销(条件判断) | ✅ 无运行时开销 |
最佳实践:
- 优先使用类型守卫,确保类型安全
- 仅在确定类型正确且无法使用类型守卫时使用类型断言
- 避免双重断言(
value as any as string),这通常是设计问题的信号
6. 可辨识联合(Discriminated Union)与类型守卫
可辨识联合(Discriminated Union) 是一种通过公共字面量属性区分联合类型的高级模式,配合类型守卫可实现完美的类型窄化。
核心思想:
- 联合类型的每个成员都有一个共同的属性(称为”判别属性”)
- 该属性的类型为字面量类型(
'success' | 'error') - 通过检查判别属性,TypeScript 能自动窄化类型
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义可辨识联合类型
type ApiResponse =
| { status: 'success'; data: any; timestamp: number }
| { status: 'error'; message: string; code: number }
| { status: 'loading' };
function handleResponse(response: ApiResponse) {
// 检查判别属性 status
if (response.status === 'success') {
// response: { status: 'success'; data: any; timestamp: number }
console.log('数据:', response.data);
console.log('时间戳:', new Date(response.timestamp).toLocaleString());
} else if (response.status === 'error') {
// response: { status: 'error'; message: string; code: number }
console.error(`错误 [${response.code}]: ${response.message}`);
} else {
// response: { status: 'loading' }
console.log('加载中...');
}
}
高级模式:嵌套可辨识联合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
// shape: { kind: 'circle'; radius: number }
return Math.PI * shape.radius ** 2;
case 'rectangle':
// shape: { kind: 'rectangle'; width: number; height: number }
return shape.width * shape.height;
case 'triangle':
// shape: { kind: 'triangle'; base: number; height: number }
return 0.5 * shape.base * shape.height;
}
}
exhaustive check(穷尽检查):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 never 类型确保处理所有联合类型分支
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return 0.5 * shape.base * shape.height;
default:
// 如果遗漏某个分支,这里会报错
return assertNever(shape);
}
}
实战案例
案例一:实现一个类型安全的 HTTP 请求函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 定义可辨识联合类型的响应
type HttpResponse<T> =
| { status: 'success'; data: T; statusCode: number }
| { status: 'error'; message: string; statusCode: number }
| { status: 'loading' }
| { status: 'idle' };
// 自定义类型守卫
function isSuccessResponse<T>(
response: HttpResponse<T>
): response is { status: 'success'; data: T; statusCode: number } {
return response.status === 'success';
}
function isErrorResponse<T>(
response: HttpResponse<T>
): response is { status: 'error'; message: string; statusCode: number } {
return response.status === 'error';
}
// 类型安全的 HTTP 请求函数
async function fetchData<T>(url: string): Promise<HttpResponse<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
return {
status: 'error',
message: response.statusText,
statusCode: response.status
};
}
const data: T = await response.json();
return {
status: 'success',
data,
statusCode: response.status
};
} catch (error) {
return {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error',
statusCode: 0
};
}
}
// 使用示例
interface User {
id: number;
name: string;
}
async function loadUser() {
const response = await fetchData<User>('https://api.example.com/user/1');
if (isSuccessResponse(response)) {
// response: { status: 'success'; data: User; statusCode: number }
console.log(`用户: ${response.data.name}`);
} else if (isErrorResponse(response)) {
// response: { status: 'error'; message: string; statusCode: number }
console.error(`请求失败: ${response.message}`);
}
}
案例二:实现表单校验的类型守卫链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 定义表单字段类型
type FormField =
| { type: 'text'; value: string; minLength?: number }
| { type: 'number'; value: number; min?: number; max?: number }
| { type: 'email'; value: string }
| { type: 'checkbox'; checked: boolean };
// 类型守卫链
function validateField(field: FormField): string[] {
const errors: string[] = [];
switch (field.type) {
case 'text':
// field: { type: 'text'; value: string; minLength?: number }
if (field.minLength && field.value.length < field.minLength) {
errors.push(`文本长度不能少于 ${field.minLength} 个字符`);
}
break;
case 'number':
// field: { type: 'number'; value: number; min?: number; max?: number }
if (field.min !== undefined && field.value < field.min) {
errors.push(`数值不能小于 ${field.min}`);
}
if (field.max !== undefined && field.value > field.max) {
errors.push(`数值不能大于 ${field.max}`);
}
break;
case 'email':
// field: { type: 'email'; value: string }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(field.value)) {
errors.push('邮箱格式不正确');
}
break;
case 'checkbox':
// field: { type: 'checkbox'; checked: boolean }
if (!field.checked) {
errors.push('必须勾选同意条款');
}
break;
}
return errors;
}
// 使用示例
const formFields: FormField[] = [
{ type: 'text', value: 'Hello', minLength: 10 },
{ type: 'number', value: 150, min: 0, max: 100 },
{ type: 'email', value: 'invalid-email' },
{ type: 'checkbox', checked: false }
];
formFields.forEach((field, index) => {
const errors = validateField(field);
if (errors.length > 0) {
console.log(`字段 ${index + 1} 校验失败:`, errors);
}
});
案例三:实现可辨识联合状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 定义状态机的状态和事件
type State =
| { status: 'idle' }
| { status: 'loading'; startTime: number }
| { status: 'success'; data: string; fetchedAt: number }
| { status: 'error'; error: string; retryCount: number };
type Event =
| { type: 'FETCH' }
| { type: 'SUCCESS'; data: string }
| { type: 'FAILURE'; error: string }
| { type: 'RETRY' }
| { type: 'RESET' };
// 状态机转换器
function reducer(state: State, event: Event): State {
switch (state.status) {
case 'idle':
if (event.type === 'FETCH') {
return { status: 'loading', startTime: Date.now() };
}
return state;
case 'loading':
if (event.type === 'SUCCESS') {
return {
status: 'success',
data: event.data,
fetchedAt: Date.now()
};
}
if (event.type === 'FAILURE') {
return {
status: 'error',
error: event.error,
retryCount: 0
};
}
return state;
case 'success':
if (event.type === 'RESET') {
return { status: 'idle' };
}
return state;
case 'error':
if (event.type === 'RETRY') {
return { status: 'loading', startTime: Date.now() };
}
if (event.type === 'RESET') {
return { status: 'idle' };
}
return state;
default:
// 穷尽检查
const _exhaustiveCheck: never = state;
return _exhaustiveCheck;
}
}
// 使用示例
let currentState: State = { status: 'idle' };
currentState = reducer(currentState, { type: 'FETCH' });
console.log('当前状态:', currentState);
// 输出: { status: 'loading', startTime: 1620000000000 }
currentState = reducer(currentState, { type: 'SUCCESS', data: 'Hello World' });
console.log('当前状态:', currentState);
// 输出: { status: 'success', data: 'Hello World', fetchedAt: 1620000001000 }
底层原理
编译时窄化机制
TypeScript 的类型守卫依赖于控制流分析(Control Flow Analysis):
- 类型窄化(Type Narrowing):
- 当 TypeScript 编译器遇到类型守卫表达式时,会在条件为
true的代码路径中更新变量的类型信息 - 窄化是局部作用域内的临时类型推断,不会影响原始声明
- 当 TypeScript 编译器遇到类型守卫表达式时,会在条件为
- 类型收缩算法: ``` 输入: 联合类型 T = A | B | C 守卫: condition x => x is A 输出:
- 若 condition 为 true: 类型收缩为 A
- 若 condition 为 false: 类型收缩为 B | C ```
类型谓词的推断过程
当函数返回类型为 parameter is Type 时,TypeScript 编译器:
- 标记函数为类型谓词函数
- 在条件判断中,如果该函数在
if语句中被调用且返回true,则:- 将参数类型窄化为
Type - 更新当前作用域的类型上下文
- 将参数类型窄化为
- 类型谓词优先于函数体的实际返回值类型检查
1
2
3
4
5
6
7
8
// 编译器的推断过程示例
function isString(value: any): value is string {
return typeof value === 'string';
}
// 编译器内部处理:
// 1. 检查 isString 的返回类型是否为类型谓词
// 2. 如果是,则在 if (isString(x)) 分支中,将 x 的类型窄化为 string
Control Flow Analysis 详解
TypeScript 使用数据流分析追踪每个变量的类型变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function example(x: string | number) {
// 初始: x: string | number
if (typeof x === 'string') {
// 分支1: x: string
console.log(x.toUpperCase());
} else {
// 分支2: x: number
console.log(x.toFixed(2));
}
// 合并点: x: string | number(类型重新宽化)
console.log(x);
}
分析步骤:
- 初始化:变量
x的类型为声明类型string | number - 分支分析:在
if分支中,x的类型被窄化为string;在else分支中,窄化为number - 类型合并:在条件块之后,TypeScript 合并所有分支的类型,恢复为原始联合类型
💡 人话总结: 类型守卫就像是一个”类型侦探”,在代码执行的过程中,通过 typeof、instanceof、in 这些”线索”,智能判断变量到底是什么类型。它让 TypeScript 编译器能够”看懂”你的条件判断,自动帮你缩小类型范围,避免你手动写一大堆类型断言。自定义类型谓词 is 则是让你自己定义”侦探规则”,告诉编译器:”相信我,如果这个函数返回 true,那这个变量一定是这个类型!”
高频面试题解析
Q1:TypeScript 中有哪些类型守卫方式?
答案: TypeScript 提供以下几种类型守卫方式:
- typeof 守卫:用于基本数据类型检查
1
if (typeof value === 'string') { /* ... */ }
- instanceof 守卫:用于自定义类或内置对象检查
1
if (value instanceof Date) { /* ... */ }
- in 守卫:用于对象属性存在性检查
1
if ('property' in object) { /* ... */ }
- 自定义类型谓词(is):灵活的用户自定义守卫
1
function isType(value: any): value is Type { /* ... */ }
- 可辨识联合(Discriminated Union):通过公共字面量属性区分类型
1
if (response.status === 'success') { /* ... */ }
- 相等性缩小(Equality Narrowing):
1
if (value === 'literal') { /* value 被窄化为 'literal' */ }
Q2:类型谓词 is 和类型断言 as 有什么区别?
答案:
| 对比维度 | 类型谓词 is | 类型断言 as |
|---|---|---|
| 运行时检查 | ✅ 有(函数体内实现) | ❌ 无 |
| 安全性 | ✅ 高(编译时+运行时) | ⚠️ 低(仅编译时) |
| 使用场景 | 条件判断中的类型窄化 | 确定类型但编译器无法推断 |
| 代码复用 | ✅ 可封装为函数复用 | ❌ 每次使用需重复断言 |
| 错误处理 | ✅ 可在函数内统一处理 | ❌ 需在使用处分别处理 |
示例对比:
1
2
3
4
5
6
7
8
9
10
11
// 类型谓词:安全且可复用
function isString(value: any): value is string {
return typeof value === 'string';
}
if (isString(maybeString)) {
// 安全:maybeString 已被确认为 string
}
// 类型断言:不安全且冗余
const str = maybeString as string; // ⚠️ 如果 maybeString 不是 string,运行时可能出错
Q3:什么是可辨识联合类型?为什么配合类型守卫使用?
答案:
可辨识联合类型是一种通过公共判别属性(通常为字面量类型)区分的联合类型。
优势:
- 类型安全:编译时确保处理所有联合成员
- 代码清晰:通过判别属性快速识别类型
- 自动窄化:TypeScript 能根据判别属性自动窄化类型
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
type Result =
| { status: 'success'; data: any }
| { status: 'error'; message: string };
function handleResult(result: Result) {
if (result.status === 'success') {
// result 自动窄化为 { status: 'success'; data: any }
console.log(result.data);
} else {
// result 自动窄化为 { status: 'error'; message: string }
console.error(result.message);
}
}
为什么不配合类型守卫? 实际上,可辨识联合就是一种类型守卫模式。result.status === 'success' 本身就是一个类型守卫,TypeScript 能根据这个条件自动窄化类型。
Q4:如何实现一个数组类型过滤函数?
答案:
使用类型谓词 is 可以实现类型安全的数组过滤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 基础版本:过滤出所有数字
function filterNumbers(arr: (number | string)[]): number[] {
return arr.filter((item): item is number => typeof item === 'number');
}
// 通用版本:类型守卫过滤
function filterByType<T>(
arr: any[],
guard: (item: any) => item is T
): T[] {
return arr.filter(guard);
}
// 使用示例
const mixedArray = [1, 'hello', 2, 'world', 3];
const numbers = filterByType(mixedArray, (item): item is number =>
typeof item === 'number'
);
console.log(numbers); // 输出: [1, 2, 3]
const strings = filterByType(mixedArray, (item): item is string =>
typeof item === 'string'
);
console.log(strings); // 输出: ['hello', 'world']
高级应用:多个类型守卫组合:
1
2
3
4
5
6
7
function isNonNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const arrayWithNulls = [1, null, 2, undefined, 3];
const nonNullNumbers = arrayWithNulls.filter(isNonNull);
console.log(nonNullNumbers); // 输出: [1, 2, 3]
总结与扩展
核心要点
- 类型守卫是 TypeScript 类型系统的核心特性,通过编译时类型窄化提升代码安全性
- 四种主要守卫方式:
typeof:基本类型检查instanceof:对象实例检查in:属性存在性检查- 自定义类型谓词
is:灵活的用户定义守卫
- 可辨识联合是类型守卫的最佳实践,通过公共判别属性实现类型自动窄化
- 类型守卫 vs 类型断言:优先使用类型守卫,确保类型安全
- 控制流分析是类型守卫的底层实现机制,通过追踪代码执行路径实现类型窄化
延伸学习方向
- 高级类型编程:
- 条件类型(Conditional Types)
- 映射类型(Mapped Types)← 上一篇主题
- 类型推断(infer 关键字)← 下一篇主题
- 类型守卫的边界情况:
- 自定义类型守卫的性能优化
- 复杂嵌套对象的类型守卫
- 异步类型守卫的实现
- 实际应用:
- 在 React 组件中使用类型守卫
- 在 Node.js 中处理不确定类型的输入
- 构建类型安全的 API 客户端
相关主题
参考资料:
- TypeScript 官方文档 - Type Guards and Differentiating Types
- 《Programming TypeScript》 by Boris Cherny
- TypeScript Deep Dive - Type Guards
- Microsoft TypeScript GitHub - Control Flow Analysis
工具推荐:
- TypeScript Playground:在线测试类型守卫效果
- TSConfig 参考:strictNullChecks 对类型守卫的影响
- ESLint 规则:@typescript-eslint/strict-boolean-expressions
作者注:类型守卫是 TypeScript 中最实用且最容易忽视的高级特性之一。掌握它不仅能提升代码质量,还能在面试中展现你对类型系统的深刻理解。