指南

类型组合与类型保护

本文详细介绍 TypeScript 中联合类型与交叉类型的区别与应用,类型断言的正确使用,以及常见类型守卫技巧,帮助前端小白全面掌握类型相关知识。

🎯 引言

学会这篇文章,你将能够理解并灵活运用 TypeScript 中的联合类型和交叉类型,掌握类型断言的使用技巧,以及通过 typeof、instanceof 和自定义类型守卫有效区分不同类型。文章还会带你深入理解字面量类型的作用,帮助你写出更安全、更高效的前端代码。


🔀 联合类型(Union Types)与交叉类型(Intersection Types)

在开始之前,我们先说明这两个类型的区别,虽然名字相似,功能却大不相同。

联合类型用 | 表示,表示变量可以是多种类型之一。举个例子,变量既可以是字符串,也可以是数字。

交叉类型用 & 表示,表示变量同时满足多个类型的要求,合并多个类型的属性。

联合类型示例

type ID = number | string;

function printId(id: ID) {
    console.log('你的ID是:' + id);
}

printId(123);
printId('abc123');

这段代码中,id 参数可以是数字也可以是字符串,调用时不限制类型,只要是这两种之一就行。

交叉类型示例

interface Person {
    name: string;
    age: number;
}

interface Employee {
    employeeId: number;
}

type EmployeePerson = Person & Employee;

const emp: EmployeePerson = {
    name: '张三',
    age: 30,
    employeeId: 101,
};

这里 EmployeePerson 必须同时拥有 PersonEmployee 的属性。

交叉类型通常用于合并对象类型,适合描述一个对象拥有多个职责。联合类型更灵活,适合函数参数可能接受多种类型的场景。

✨ 类型断言(as)

类型断言可以帮助你告诉编译器变量的具体类型,类似于“我知道这个变量的类型,你不用担心”。

基本用法

let someValue: unknown = '这是一段字符串';

let strLength: number = (someValue as string).length;

这里 someValue 原本是 unknown 类型,通过 as string 告诉编译器它是字符串,从而访问 length 属性不会报错。

类型断言不是类型转换,只是告诉编译器“相信我,我知道类型”,如果断言错误,运行时可能会出错。

🔍 类型守卫

类型守卫是 TypeScript 用来缩小联合类型范围的技术,帮我们安全地访问特定类型的属性或方法。

typeof 类型守卫

typeof 用于区分基础类型,如 "string", "number", "boolean"

function double(value: number | string) {
    if (typeof value === 'string') {
        return value.repeat(2);
    } else {
        return value * 2;
    }
}

这里根据 typeof 来判断是字符串还是数字,分别执行不同操作。

instanceof 类型守卫

instanceof 用于判断对象实例的类型,常见于类或构造函数。

class Dog {
    bark() {
        console.log('汪汪');
    }
}

class Cat {
    meow() {
        console.log('喵喵');
    }
}

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

自定义类型守卫

有时候需要手动写守卫函数,返回值是类型谓词 param is Type

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();
    } else {
        pet.fly();
    }
}
类型守卫让代码更安全,避免运行时错误。合理使用类型守卫可以让代码逻辑清晰,易于维护。

🏷️ 字面量类型(Literal Types)

字面量类型是指变量的类型限定为某个具体值,如 "left""right"

使用场景举例

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

function move(direction: Direction) {
    console.log('移动方向:' + direction);
}

move('left'); // 正确
// move("forward"); // 错误,编译报错

这样就限定了参数只能是指定的几个方向,防止传入不合法的值。

字面量类型提高了代码的类型安全,让代码更加自解释。
在设计组件或函数时,用字面量类型限定参数是一个好习惯。

🧾 小节总结

  • 联合类型用 | 表示,值可以是多种类型之一,灵活处理多种输入。
  • 交叉类型用 & 表示,合并多个类型属性,适合多个职责合一的对象。
  • 类型断言 as 用来告诉编译器变量具体类型,谨慎使用避免运行错误。
  • typeofinstanceof 是常用的类型守卫,帮助区分不同类型。
  • 自定义类型守卫函数提高代码可读性和安全性。
  • 字面量类型限定具体值,提高了类型安全和代码质量。

❓ 知识问答(Q&A)

Q:联合类型和交叉类型的根本区别是什么?

A:联合类型表示变量可能属于多种类型之一,交叉类型则表示变量同时满足多个类型的要求。

Q:类型断言和类型转换有什么区别?

A:类型断言只是告诉编译器变量的类型,编译时有效,运行时无影响;类型转换是运行时的实际转换。

Q:什么时候需要写自定义类型守卫?

A:当 typeofinstanceof 不能准确识别类型时,比如接口类型的区分,就需要自定义类型守卫。


🧪 小练习

练习 1

请写一个函数,接收参数类型为 string | number | boolean,根据类型不同返回不同结果,比如字符串返回长度,数字返回平方,布尔值返回字符串 "true""false"

function processValue(value: string | number | boolean) {
    // 在这里补充代码
}

练习2

定义两个接口 SquareCircle,分别有边长和半径属性,写一个函数接收它们的交叉类型,返回它们的面积。

interface Square {
    sideLength: number;
}

interface Circle {
    radius: number;
}

type Shape = Square & Circle;

function getArea(shape: Shape) {
    // 在这里补充代码
}

🎉 恭喜你已经掌握了类型组合(联合类型 & 交叉类型)、类型断言、类型守卫和字面量类型的核心技能啦!这些都是写安全、健壮 TypeScript 代码的基础武器,继续练习,前端路上更自信!