TypeScript类型守卫 #

一、类型守卫概述 #

1.1 什么是类型守卫 #

类型守卫是TypeScript中用于在运行时检查类型,从而在编译时收窄类型的表达式。

typescript
function process(value: string | number): void {
    if (typeof value === 'string') {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

1.2 类型收窄 #

类型守卫会将类型从宽泛的类型收窄到更具体的类型:

typescript
let value: string | number | boolean;

if (typeof value === 'string') {
    console.log(value.toUpperCase());
} else if (typeof value === 'number') {
    console.log(value.toFixed(2));
} else {
    console.log(value);
}

二、typeof类型守卫 #

2.1 基本用法 #

typescript
function process(value: string | number): void {
    if (typeof value === 'string') {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

2.2 typeof支持的类型 #

typeof可以检测以下类型:

  • 'string'
  • 'number'
  • 'boolean'
  • 'symbol'
  • 'bigint'
  • 'undefined'
  • 'object'
  • 'function'
typescript
function inspect(value: unknown): void {
    if (typeof value === 'string') {
        console.log('String:', value);
    } else if (typeof value === 'number') {
        console.log('Number:', value);
    } else if (typeof value === 'boolean') {
        console.log('Boolean:', value);
    } else if (typeof value === 'object') {
        console.log('Object:', value);
    } else if (typeof value === 'function') {
        console.log('Function:', value);
    } else {
        console.log('Unknown type');
    }
}

2.3 typeof的局限性 #

typeof null返回'object'

typescript
function process(value: object | null): void {
    if (typeof value === 'object') {
        console.log(value.toString());
    }
}

function processSafe(value: object | null): void {
    if (value !== null && typeof value === 'object') {
        console.log(value.toString());
    }
}

三、instanceof类型守卫 #

3.1 基本用法 #

typescript
class Dog {
    bark(): void {
        console.log('Woof!');
    }
}

class Cat {
    meow(): void {
        console.log('Meow!');
    }
}

function makeSound(animal: Dog | Cat): void {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

3.2 内置类型 #

typescript
function process(value: Date | string): void {
    if (value instanceof Date) {
        console.log(value.toISOString());
    } else {
        console.log(value.toUpperCase());
    }
}

function processError(error: Error | string): void {
    if (error instanceof Error) {
        console.log(error.message);
    } else {
        console.log(error);
    }
}

3.3 数组检查 #

typescript
function process(value: string | string[]): void {
    if (Array.isArray(value)) {
        value.forEach(item => console.log(item));
    } else {
        console.log(value);
    }
}

四、in操作符类型守卫 #

4.1 基本用法 #

typescript
interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function move(animal: Bird | Fish): void {
    if ('fly' in animal) {
        animal.fly();
    } else {
        animal.swim();
    }
}

4.2 检查可选属性 #

typescript
interface User {
    name: string;
    email?: string;
}

function process(user: User): void {
    if ('email' in user) {
        console.log(user.email);
    }
}

4.3 检查方法存在 #

typescript
interface Serializable {
    serialize(): string;
}

function process(value: object): void {
    if ('serialize' in value && typeof value.serialize === 'function') {
        console.log(value.serialize());
    }
}

五、字面量类型守卫 #

5.1 字符串字面量 #

typescript
type Direction = 'up' | 'down' | 'left' | 'right';

function move(direction: Direction): void {
    switch (direction) {
        case 'up':
            console.log('Moving up');
            break;
        case 'down':
            console.log('Moving down');
            break;
        case 'left':
            console.log('Moving left');
            break;
        case 'right':
            console.log('Moving right');
            break;
    }
}

5.2 可辨识联合 #

typescript
interface Circle {
    kind: 'circle';
    radius: number;
}

interface Square {
    kind: 'square';
    sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.sideLength ** 2;
    }
}

5.3 穷尽性检查 #

typescript
function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.sideLength ** 2;
        default:
            const _exhaustiveCheck: never = shape;
            throw new Error(`Unknown shape: ${_exhaustiveCheck}`);
    }
}

六、自定义类型守卫 #

6.1 类型谓词 #

使用is关键字定义类型谓词:

typescript
function isString(value: unknown): value is string {
    return typeof value === 'string';
}

function process(value: unknown): void {
    if (isString(value)) {
        console.log(value.toUpperCase());
    }
}

6.2 对象类型守卫 #

typescript
interface User {
    name: string;
    age: number;
}

function isUser(value: unknown): value is User {
    return (
        typeof value === 'object' &&
        value !== null &&
        'name' in value &&
        'age' in value &&
        typeof (value as User).name === 'string' &&
        typeof (value as User).age === 'number'
    );
}

function process(value: unknown): void {
    if (isUser(value)) {
        console.log(value.name, value.age);
    }
}

6.3 数组类型守卫 #

typescript
function isStringArray(value: unknown): value is string[] {
    return Array.isArray(value) && value.every(item => typeof item === 'string');
}

function process(value: unknown): void {
    if (isStringArray(value)) {
        value.forEach(item => console.log(item.toUpperCase()));
    }
}

6.4 断言函数 #

typescript
function assertIsString(value: unknown): asserts value is string {
    if (typeof value !== 'string') {
        throw new Error('Value is not a string');
    }
}

function process(value: unknown): void {
    assertIsString(value);
    console.log(value.toUpperCase());
}

6.5 断言函数与错误 #

typescript
function assertDefined<T>(value: T | null | undefined, message?: string): asserts value is T {
    if (value === null || value === undefined) {
        throw new Error(message ?? 'Value is null or undefined');
    }
}

function process(value: string | null): void {
    assertDefined(value);
    console.log(value.toUpperCase());
}

七、真值检查 #

7.1 真值收窄 #

typescript
function process(value: string | null | undefined): void {
    if (value) {
        console.log(value.toUpperCase());
    }
}

7.2 排除null和undefined #

typescript
function process(value: string | null | undefined): string {
    if (value == null) {
        return 'default';
    }
    return value.toUpperCase();
}

7.3 Boolean转换 #

typescript
function process(values: (string | null | undefined)[]): string[] {
    return values.filter(Boolean);
}

const filtered = process(['a', null, 'b', undefined, 'c']);
console.log(filtered);

八、相等性收窄 #

8.1 相等检查 #

typescript
function process(value: string | number, expected: string): void {
    if (value === expected) {
        console.log(value.toUpperCase());
    }
}

8.2 switch语句 #

typescript
type Status = 'pending' | 'approved' | 'rejected';

function handleStatus(status: Status): string {
    switch (status) {
        case 'pending':
            return 'Waiting for approval';
        case 'approved':
            return 'Approved';
        case 'rejected':
            return 'Rejected';
    }
}

九、实用示例 #

9.1 API响应处理 #

typescript
interface SuccessResponse<T> {
    success: true;
    data: T;
}

interface ErrorResponse {
    success: false;
    error: string;
}

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

function isSuccess<T>(response: ApiResponse<T>): response is SuccessResponse<T> {
    return response.success === true;
}

async function fetchData<T>(url: string): Promise<T> {
    const response = await fetch(url);
    const data: ApiResponse<T> = await response.json();
    
    if (isSuccess(data)) {
        return data.data;
    }
    
    throw new Error(data.error);
}

9.2 表单验证 #

typescript
interface ValidForm {
    isValid: true;
    values: Record<string, string>;
}

interface InvalidForm {
    isValid: false;
    errors: Record<string, string>;
}

type FormResult = ValidForm | InvalidForm;

function validateForm(formData: FormData): FormResult {
    const values: Record<string, string> = {};
    const errors: Record<string, string> = {};
    
    formData.forEach((value, key) => {
        if (typeof value === 'string') {
            if (value.trim() === '') {
                errors[key] = 'This field is required';
            } else {
                values[key] = value;
            }
        }
    });
    
    if (Object.keys(errors).length === 0) {
        return { isValid: true, values };
    }
    
    return { isValid: false, errors };
}

function processForm(result: FormResult): void {
    if (result.isValid) {
        console.log('Submitting:', result.values);
    } else {
        console.log('Errors:', result.errors);
    }
}

十、总结 #

本章介绍了TypeScript类型守卫:

类型守卫类型 #

  1. typeof:检查原始类型
  2. instanceof:检查类实例
  3. in:检查属性存在
  4. 字面量:检查具体值
  5. 自定义:使用类型谓词

最佳实践 #

  1. 使用可辨识联合提高类型安全性
  2. 创建可复用的类型守卫函数
  3. 使用断言函数进行强制检查
  4. 在switch语句中进行穷尽性检查
最后更新:2026-03-26