TypeScript联合类型详解
编程小白的TypeScript联合类型知识手册
一、联合类型是什么?
联合类型是TypeScript中的一个强大特性,它允许你定义一个变量可以是多种类型中的一种。
🔍 大白话解释:
想象你有一个盒子,这个盒子可以装苹果🍎或者香蕉🍌,但不能同时装两种水果。联合类型就像是给这个盒子贴上一个标签:”苹果 | 香蕉”,告诉别人这个盒子只能放这两种水果中的一种。
// 定义一个变量可以是number或string类型
let age: number | string;
age = 25; // 正确,number类型
age = “二十五”; // 正确,string类型
age = true; // 错误!boolean类型不符合要求
let age: number | string;
age = 25; // 正确,number类型
age = “二十五”; // 正确,string类型
age = true; // 错误!boolean类型不符合要求
二、联合类型的基本用法
1. 定义联合类型
使用竖线 |
分隔多个类型:
type ID = number | string; // ID可以是数字或字符串
let userId: ID = 123; // 正确
let orderId: ID = “ORD-456”; // 正确
let userId: ID = 123; // 正确
let orderId: ID = “ORD-456”; // 正确
2. 联合类型与函数参数
函数参数使用联合类型时,可以接受多种类型的值:
function printId(id: number | string) {
console.log(`ID: ${id}`);
}
printId(101); // 正确
printId(“C101”); // 正确
console.log(`ID: ${id}`);
}
printId(101); // 正确
printId(“C101”); // 正确
⚠️ 重要提示:
当你使用联合类型时,只能访问这些类型共有的方法和属性。
function getLength(value: string | number) {
// 错误!因为number类型没有length属性
console.log(value.length);
}
// 错误!因为number类型没有length属性
console.log(value.length);
}
上面代码会报错,因为number
类型没有length
属性。
三、类型守卫 – 安全使用联合类型
为了解决上面提到的问题,我们需要使用类型守卫来缩小类型范围。
1. typeof 类型守卫
使用JavaScript的typeof
操作符检查类型:
function getLength(value: string | number) {
if (typeof value === “string”) {
// 这里value被确定为string类型
console.log(value.length);
} else {
// 这里value被确定为number类型
console.log(value.toString().length);
}
}
if (typeof value === “string”) {
// 这里value被确定为string类型
console.log(value.length);
} else {
// 这里value被确定为number类型
console.log(value.toString().length);
}
}
2. 自定义类型守卫
对于自定义类型(如接口),可以使用in
操作符:
interface Circle {
kind: “circle”;
radius: number;
}
interface Square {
kind: “square”;
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === “circle”) {
// 这里shape被确定为Circle类型
return Math.PI * shape.radius ** 2;
} else {
// 这里shape被确定为Square类型
return shape.sideLength ** 2;
}
}
kind: “circle”;
radius: number;
}
interface Square {
kind: “square”;
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === “circle”) {
// 这里shape被确定为Circle类型
return Math.PI * shape.radius ** 2;
} else {
// 这里shape被确定为Square类型
return shape.sideLength ** 2;
}
}
💡 小贴士:
上面的例子使用了可辨识联合模式,通过公共的kind
属性来区分不同的类型。
四、联合类型的高级用法
1. 联合类型与数组
可以创建包含多种类型元素的数组:
// 数组元素可以是数字或字符串
let mixedArray: (number | string)[] = [1, “two”, 3, “four”];
// 访问元素时,TypeScript会提示联合类型的方法
mixedArray[0].toString(); // 正确,所有类型都有toString方法
mixedArray[1].toFixed(2); // 错误!字符串没有toFixed方法
let mixedArray: (number | string)[] = [1, “two”, 3, “four”];
// 访问元素时,TypeScript会提示联合类型的方法
mixedArray[0].toString(); // 正确,所有类型都有toString方法
mixedArray[1].toFixed(2); // 错误!字符串没有toFixed方法
2. 联合类型与类型别名
结合使用type
关键字,让代码更清晰:
type Status = “pending” | “approved” | “rejected”;
function handleStatus(status: Status) {
// 这里只能传入上面定义的三种字符串之一
console.log(`当前状态:${status}`);
}
handleStatus(“pending”); // 正确
handleStatus(“processing”); // 错误!
function handleStatus(status: Status) {
// 这里只能传入上面定义的三种字符串之一
console.log(`当前状态:${status}`);
}
handleStatus(“pending”); // 正确
handleStatus(“processing”); // 错误!
3. 联合类型与字面量类型
联合类型可以包含具体的值,这称为字面量类型:
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceValue {
return (Math.floor(Math.random() * 6) + 1) as DiceValue;
}
let dice: DiceValue = 4; // 正确
dice = 7; // 错误!只能是1-6之间的数字
function rollDice(): DiceValue {
return (Math.floor(Math.random() * 6) + 1) as DiceValue;
}
let dice: DiceValue = 4; // 正确
dice = 7; // 错误!只能是1-6之间的数字
🌰 实际应用示例:
联合类型在React组件中非常有用:
type ButtonSize = “small” | “medium” | “large”;
type ButtonType = “primary” | “secondary” | “danger”;
interface ButtonProps {
size?: ButtonSize;
type?: ButtonType;
onClick: () => void;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({ size = ‘medium’, type = ‘primary’, …props }) => {
// 组件实现…
}
type ButtonType = “primary” | “secondary” | “danger”;
interface ButtonProps {
size?: ButtonSize;
type?: ButtonType;
onClick: () => void;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({ size = ‘medium’, type = ‘primary’, …props }) => {
// 组件实现…
}
五、常见问题与解决方案
1. 类型守卫不够严谨
function printValue(value: string | number | boolean) {
if (typeof value === “string”) {
console.log(value.toUpperCase());
} else if (typeof value === “number”) {
console.log(value.toFixed(2));
}
// 忘记了处理boolean类型!
}
if (typeof value === “string”) {
console.log(value.toUpperCase());
} else if (typeof value === “number”) {
console.log(value.toFixed(2));
}
// 忘记了处理boolean类型!
}
解决方案: 使用更严格的条件判断或添加default分支
2. 类型断言过度使用
let input: string | number = “123”;
// 强制类型断言,但可能不安全
let length: number = (input as string).length;
// 强制类型断言,但可能不安全
let length: number = (input as string).length;
解决方案: 尽量使用类型守卫代替类型断言
3. 联合类型与重载混淆
⚠️ 注意:
联合类型不同于函数重载:
• 联合类型:一个参数可以是多种类型
• 函数重载:多个函数签名,根据参数类型决定调用哪个实现
六、最佳实践
- 优先使用联合类型而不是any类型,保持类型安全
- 对超过3种的联合类型考虑使用可辨识联合模式
- 使用类型别名给复杂联合类型命名,提高可读性
- 处理联合类型时,始终包含所有可能的类型分支
- 在React中,联合类型非常适合组件的状态和属性定义
💡 小贴士:
使用VS Code等编辑器时,悬停在联合类型变量上可以查看所有可能的类型,这对开发非常有帮助!