Appearance
TypeScript 联合类型
在 TypeScript 中,联合类型(Union Types)是一种特殊的类型,表示一个值可以是多种类型中的一种。联合类型使用竖线 | 来分隔不同的类型,例如 string | number 表示一个值可以是字符串或数字。本文将详细介绍 TypeScript 中的联合类型。
1. 基本用法
声明联合类型
联合类型的声明使用竖线 | 来分隔不同的类型。
typescript
// 字符串或数字类型
let value: string | number;
// 布尔值或字符串类型
let flag: boolean | string;
// 多种类型的联合
let mixed: string | number | boolean | null | undefined;赋值
联合类型的变量可以赋值为联合类型中的任何一种类型。
typescript
let value: string | number;
value = "hello"; // 正确:字符串类型
value = 123; // 正确:数字类型
// 错误:类型不匹配
// value = true; // 类型 'boolean' 不能赋值给类型 'string | number'函数参数
函数参数可以使用联合类型,允许传入多种类型的参数。
typescript
function printValue(value: string | number): void {
console.log(value);
}
printValue("hello"); // 正确:字符串类型
printValue(123); // 正确:数字类型
// 错误:类型不匹配
// printValue(true); // 类型 'boolean' 不能赋值给类型 'string | number'函数返回值
函数返回值也可以使用联合类型,表示函数可能返回多种类型的值。
typescript
function getValue(flag: boolean): string | number {
return flag ? "hello" : 123;
}
let result: string | number = getValue(true);
console.log(result); // 输出:hello
result = getValue(false);
console.log(result); // 输出:1232. 类型注解
基本类型联合
typescript
// 字符串或数字
let value: string | number;
// 布尔值或字符串
let flag: boolean | string;
// 数字或布尔值
let numberOrBoolean: number | boolean;复杂类型联合
typescript
// 数组或对象
let arrayOrObject: number[] | { name: string };
// 函数或字符串
let functionOrString: (() => void) | string;
// 接口或类
interface User { id: number; name: string; }
class Person { name: string; age: number; }
let userOrPerson: User | Person;字面量类型联合
typescript
// 字符串字面量联合
let direction: "up" | "down" | "left" | "right";
direction = "up"; // 正确
direction = "left"; // 正确
// 错误:类型不匹配
// direction = "forward"; // 类型 '"forward"' 不能赋值给类型 '"up" | "down" | "left" | "right"'
// 数字字面量联合
let dice: 1 | 2 | 3 | 4 | 5 | 6;
dice = 1; // 正确
dice = 6; // 正确
// 布尔字面量联合
let booleanLiteral: true | false;
booleanLiteral = true; // 正确
booleanLiteral = false; // 正确嵌套联合类型
typescript
// 嵌套联合类型
let nested: (string | number) | boolean;
// 复杂嵌套
let complex: string | (number | boolean)[];3. 类型守卫
在 TypeScript 中,当使用联合类型时,需要使用类型守卫来确定具体的类型,以便进行相应的操作。
typeof 类型守卫
使用 typeof 操作符来检查值的类型。
typescript
function processValue(value: string | number): void {
if (typeof value === "string") {
// 在这个分支中,TypeScript 知道 value 是 string 类型
console.log(value.toUpperCase());
} else {
// 在这个分支中,TypeScript 知道 value 是 number 类型
console.log(value.toFixed(2));
}
}
processValue("hello"); // 输出:HELLO
processValue(123.45); // 输出:123.45instanceof 类型守卫
使用 instanceof 操作符来检查对象的类型。
typescript
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function processPet(pet: Dog | Cat): void {
if (pet instanceof Dog) {
// 在这个分支中,TypeScript 知道 pet 是 Dog 类型
pet.bark();
} else {
// 在这个分支中,TypeScript 知道 pet 是 Cat 类型
pet.meow();
}
}
processPet(new Dog()); // 输出:Woof!
processPet(new Cat()); // 输出:Meow!字面量类型守卫
使用字面量类型来检查值的具体值。
typescript
type Direction = "up" | "down" | "left" | "right";
function processDirection(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;
}
}
processDirection("up"); // 输出:Moving up
processDirection("left"); // 输出:Moving leftin 操作符类型守卫
使用 in 操作符来检查对象是否具有某个属性。
typescript
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function processAnimal(animal: Bird | Fish): void {
if ("fly" in animal) {
// 在这个分支中,TypeScript 知道 animal 是 Bird 类型
animal.fly();
} else {
// 在这个分支中,TypeScript 知道 animal 是 Fish 类型
animal.swim();
}
// 两者都有 layEggs 方法
animal.layEggs();
}
const bird: Bird = {
fly() { console.log("Flying"); },
layEggs() { console.log("Laying bird eggs"); }
};
const fish: Fish = {
swim() { console.log("Swimming"); },
layEggs() { console.log("Laying fish eggs"); }
};
processAnimal(bird); // 输出:Flying Laying bird eggs
processAnimal(fish); // 输出:Swimming Laying fish eggs自定义类型守卫
使用自定义函数来检查类型。
typescript
interface User {
id: number;
name: string;
}
interface Admin {
id: number;
name: string;
role: string;
}
function isAdmin(user: User | Admin): user is Admin {
return "role" in user;
}
function processUser(user: User | Admin): void {
if (isAdmin(user)) {
// 在这个分支中,TypeScript 知道 user 是 Admin 类型
console.log(`Admin ${user.name} with role ${user.role}`);
} else {
// 在这个分支中,TypeScript 知道 user 是 User 类型
console.log(`User ${user.name}`);
}
}
const user: User = { id: 1, name: "John" };
const admin: Admin = { id: 2, name: "Jane", role: "admin" };
processUser(user); // 输出:User John
processUser(admin); // 输出:Admin Jane with role admin4. 联合类型的操作
访问公共属性
当使用联合类型时,只能访问所有类型共有的属性和方法。
typescript
interface Bird {
fly(): void;
layEggs(): void;
name: string;
}
interface Fish {
swim(): void;
layEggs(): void;
name: string;
}
function processAnimal(animal: Bird | Fish): void {
// 可以访问公共属性 name
console.log(animal.name);
// 可以调用公共方法 layEggs
animal.layEggs();
// 错误:不能访问 Bird 特有的方法 fly
// animal.fly();
// 错误:不能访问 Fish 特有的方法 swim
// animal.swim();
}类型断言
使用类型断言来指定具体的类型。
typescript
function processValue(value: string | number): void {
// 使用类型断言
const strValue = value as string;
console.log(strValue.length); // 可能会出错,如果 value 是 number 类型
// 推荐:先使用类型守卫
if (typeof value === "string") {
console.log(value.length); // 安全,TypeScript 知道 value 是 string 类型
}
}
processValue("hello"); // 输出:5
// processValue(123); // 运行时错误:value.length is not a function类型缩小
TypeScript 会根据上下文自动缩小联合类型的范围。
typescript
function processValue(value: string | number | null): void {
if (value === null) {
console.log("Value is null");
return;
}
// 在这里,TypeScript 知道 value 不是 null,类型缩小为 string | number
if (typeof value === "string") {
console.log(value.length); // 安全,TypeScript 知道 value 是 string 类型
} else {
console.log(value.toFixed(2)); // 安全,TypeScript 知道 value 是 number 类型
}
}
processValue("hello"); // 输出:5
processValue(123.45); // 输出:123.45
processValue(null); // 输出:Value is null5. 联合类型的最佳实践
1. 使用类型守卫
当使用联合类型时,总是使用类型守卫来确定具体的类型,以便进行相应的操作。
typescript
// 推荐:使用类型守卫
function processValue(value: string | number): void {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
// 不推荐:使用类型断言
function processValue(value: string | number): void {
console.log((value as string).toUpperCase()); // 可能会出错
}2. 合理使用联合类型
只在必要时使用联合类型,避免过度使用导致代码复杂。
typescript
// 推荐:合理使用联合类型
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
// 不推荐:过度使用联合类型
function processValue(value: string | number | boolean | object | null | undefined): void {
// 代码会变得复杂
}3. 使用字面量类型联合
对于有限的可能值,使用字面量类型联合可以提供更精确的类型检查。
typescript
// 推荐:使用字面量类型联合
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
// 不推荐:使用字符串类型
function move(direction: string): void {
console.log(`Moving ${direction}`);
}4. 使用类型别名
对于复杂的联合类型,使用类型别名可以提高代码的可读性。
typescript
// 推荐:使用类型别名
type StringOrNumber = string | number;
function processValue(value: StringOrNumber): void {
// 代码
}
// 不推荐:直接使用复杂联合类型
function processValue(value: string | number | boolean | null | undefined): void {
// 代码
}5. 注意联合类型的顺序
在使用联合类型时,注意类型的顺序,将更具体的类型放在前面。
typescript
// 推荐:将更具体的类型放在前面
type Animal = Dog | Cat | Animal;
// 不推荐:将更一般的类型放在前面
type Animal = Animal | Dog | Cat; // 永远不会匹配到 Dog 和 Cat6. 常见错误
1. 访问非公共属性
当使用联合类型时,只能访问所有类型共有的属性和方法。
typescript
interface Bird {
fly(): void;
name: string;
}
interface Fish {
swim(): void;
name: string;
}
function processAnimal(animal: Bird | Fish): void {
// 错误:不能访问 Bird 特有的方法 fly
// animal.fly();
// 错误:不能访问 Fish 特有的方法 swim
// animal.swim();
// 正确:可以访问公共属性 name
console.log(animal.name);
}2. 类型断言错误
使用类型断言时,如果类型不匹配,会导致运行时错误。
typescript
function processValue(value: string | number): void {
// 错误:如果 value 是 number 类型,会导致运行时错误
const strValue = value as string;
console.log(strValue.length);
}
processValue(123); // 运行时错误:value.length is not a function3. 忘记使用类型守卫
当使用联合类型时,忘记使用类型守卫会导致类型错误。
typescript
function processValue(value: string | number): void {
// 错误:TypeScript 不知道 value 是 string 类型
// console.log(value.length);
// 正确:使用类型守卫
if (typeof value === "string") {
console.log(value.length);
}
}4. 联合类型与 null/undefined
当联合类型包含 null 或 undefined 时,需要处理这些情况。
typescript
function processValue(value: string | null): void {
// 错误:可能为 null
// console.log(value.length);
// 正确:处理 null 情况
if (value !== null) {
console.log(value.length);
}
}
processValue("hello"); // 输出:5
processValue(null); // 无输出5. 复杂联合类型的性能
过度使用复杂的联合类型可能会影响 TypeScript 的编译性能。
typescript
// 注意:过于复杂的联合类型可能会影响编译性能
type ComplexType = string | number | boolean | object | null | undefined | Array<string> | Array<number> | { name: string } | { age: number };7. 联合类型与其他类型的关系
联合类型与交叉类型
联合类型表示 "或" 的关系,而交叉类型表示 "与" 的关系。
typescript
// 联合类型:string 或 number
let union: string | number;
// 交叉类型:同时具有 A 和 B 的属性
interface A { a: number; }
interface B { b: string; }
let intersection: A & B = { a: 1, b: "hello" };联合类型与字面量类型
字面量类型是一种特殊的类型,表示具体的字面量值。
typescript
// 字符串字面量联合
let direction: "up" | "down" | "left" | "right";
// 数字字面量联合
let dice: 1 | 2 | 3 | 4 | 5 | 6;
// 布尔字面量联合
let booleanLiteral: true | false;联合类型与类型推断
TypeScript 会根据上下文自动推断联合类型。
typescript
// 类型推断为 string | number
let value = Math.random() > 0.5 ? "hello" : 123;
// 类型推断为 "a" | "b"
let str = Math.random() > 0.5 ? "a" : "b";8. 实际应用场景
1. 处理不同类型的输入
typescript
function formatInput(input: string | number | boolean): string {
if (typeof input === "string") {
return input.toUpperCase();
} else if (typeof input === "number") {
return input.toFixed(2);
} else {
return input ? "true" : "false";
}
}
console.log(formatInput("hello")); // 输出:HELLO
console.log(formatInput(123.456)); // 输出:123.46
console.log(formatInput(true)); // 输出:true2. 可选参数和默认值
typescript
function greet(name: string, age?: number | undefined): string {
if (age) {
return `Hello, ${name}! You are ${age} years old.`;
} else {
return `Hello, ${name}!`;
}
}
console.log(greet("John")); // 输出:Hello, John!
console.log(greet("John", 30)); // 输出:Hello, John! You are 30 years old.3. 处理不同类型的返回值
typescript
function getValue(flag: boolean): string | number {
return flag ? "hello" : 123;
}
function processResult(result: string | number): void {
if (typeof result === "string") {
console.log(`String result: ${result}`);
} else {
console.log(`Number result: ${result}`);
}
}
const result1 = getValue(true);
processResult(result1); // 输出:String result: hello
const result2 = getValue(false);
processResult(result2); // 输出:Number result: 1234. 字面量类型联合的应用
typescript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function sendRequest(url: string, method: HttpMethod): void {
console.log(`Sending ${method} request to ${url}`);
}
sendRequest("https://api.example.com/users", "GET"); // 正确
sendRequest("https://api.example.com/users", "POST"); // 正确
// 错误:类型不匹配
// sendRequest("https://api.example.com/users", "PATCH"); // 类型 '"PATCH"' 不能赋值给类型 'HttpMethod'5. 处理可选属性
typescript
interface User {
id: number;
name: string;
email?: string;
age?: number;
}
function createUser(user: User): void {
console.log(`Creating user: ${user.name}`);
if (user.email) {
console.log(`Email: ${user.email}`);
}
if (user.age) {
console.log(`Age: ${user.age}`);
}
}
createUser({ id: 1, name: "John" });
// 输出:
// Creating user: John
createUser({ id: 2, name: "Jane", email: "jane@example.com", age: 30 });
// 输出:
// Creating user: Jane
// Email: jane@example.com
// Age: 30总结
TypeScript 中的联合类型是一种强大的类型特性,它允许一个值可以是多种类型中的一种。它包括:
- 基本用法:声明联合类型,赋值,函数参数和返回值
- 类型注解:基本类型联合,复杂类型联合,字面量类型联合,嵌套联合类型
- 类型守卫:typeof 类型守卫,instanceof 类型守卫,字面量类型守卫,in 操作符类型守卫,自定义类型守卫
- 联合类型的操作:访问公共属性,类型断言,类型缩小
- 最佳实践:使用类型守卫,合理使用联合类型,使用字面量类型联合,使用类型别名,注意联合类型的顺序
- 常见错误:访问非公共属性,类型断言错误,忘记使用类型守卫,联合类型与 null/undefined,复杂联合类型的性能
- 联合类型与其他类型的关系:联合类型与交叉类型,联合类型与字面量类型,联合类型与类型推断
- 实际应用场景:处理不同类型的输入,可选参数和默认值,处理不同类型的返回值,字面量类型联合的应用,处理可选属性
通过合理使用联合类型,你可以在 TypeScript 中更灵活地处理多种类型的情况,提高代码的可读性和类型安全性。