1. 开篇:为什么我需要类型系统?
还记得我刚学习 TypeScript 时,最困惑的就是:"为什么我要多写这么多类型注解?" 直到那个让我记忆深刻的线上事故...
那天晚上11点,我被紧急电话叫醒:线上用户注册功能崩了。排查后发现,只是一个简单的类型错误:
javascript
// 之前的 JavaScript 代码
function calculateDiscount(price, discount) {
return price - (price * discount);
}
// 某个同事这样调用
const finalPrice = calculateDiscount("100", 0.2); // "10020"
如果使用 TypeScript,这个问题在编写阶段就会被发现。这就是类型系统的价值------它像是一个贴心的助手,在代码运行前就帮你找出潜在的问题。
2. 基础类型:TypeScript 的"建筑材料"
TypeScript 包含了 JavaScript 的所有基础类型,并进行了扩展。让我们一起来看看:
原始类型
javascript
// 字符串
let userName: string = "小杨";
let greeting: string = `Hello, ${userName}!`;
// 数字
let age: number = 25;
let temperature: number = -3.5;
// 布尔值
let isActive: boolean = true;
let hasPermission: boolean = false;
// null 和 undefined
let emptyValue: null = null;
let undefinedValue: undefined = undefined;
数组与元组
javascript
// 数组
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["Alice", "Bob", "Charlie"];
// 元组 - 固定长度和类型的数组
let userInfo: [string, number, boolean] = ["小杨", 25, true];
特殊类型
javascript
// any - 应急方案,但尽量少用
let uncertainValue: any = "可以是任何类型";
uncertainValue = 42;
uncertainValue = true;
// unknown - 比 any 更安全的选择
let safeUncertain: unknown = "Hello";
// safeUncertain.toUpperCase(); // 错误:需要类型检查
// void - 没有返回值
function logMessage(message: string): void {
console.log(message);
}
// never - 永远不会返回的函数
function throwError(message: string): never {
throw new Error(message);
}
3. 高级类型:让类型系统为你打工
联合类型与交叉类型
javascript
// 联合类型:可以是多种类型之一
let id: string | number = 123;
id = "ABC123"; // 这也是合法的
// 交叉类型:合并多个类型
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: string;
department: string;
}
type Staff = Person & Employee;
const staffMember: Staff = {
name: "小杨",
age: 25,
employeeId: "E001",
department: "Engineering"
};
字面量类型
javascript
// 具体的值作为类型
let direction: "left" | "right" | "up" | "down" = "left";
let status: "success" | "error" | "loading" = "loading";
// 在实际项目中的应用
interface ApiResponse {
status: "success" | "error";
data?: any;
message?: string;
}
4. 类型别名 vs 接口:相似却不同的双胞胎
刚开始学习时,我经常困惑什么时候该用 type,什么时候该用 interface。经过实践,我总结出了它们的区别
javascript
// 类型别名
type UserID = string | number;
type UserRole = "admin" | "user" | "guest";
type UserPreferences = {
theme: "light" | "dark";
language: string;
notifications: boolean;
};
// 接口
interface IUser {
id: UserID;
name: string;
email: string;
role: UserRole;
preferences: UserPreferences;
}
// 接口可以扩展
interface IAdminUser extends IUser {
permissions: string[];
canManageUsers: boolean;
}
我的使用经验:
- 使用 interface 定义对象形状
- 使用 type 定义联合类型、元组或复杂类型组合
- 需要扩展时优先考虑 interface
5. 泛型:编写灵活且安全的代码
泛型是我觉得最强大的特性之一,它让组件可以支持多种类型,同时保持类型安全。
javascript
// 基础泛型函数
function identity<T>(value: T): T {
return value;
}
// 使用
const stringValue = identity("hello"); // 类型推断为 string
const numberValue = identity<number>(42); // 显式指定类型
// 泛型接口
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
// 使用泛型接口
const userResponse: ApiResponse<IUser> = {
success: true,
data: {
id: 1,
name: "小杨",
email: "yang@example.com",
role: "admin",
preferences: {
theme: "dark",
language: "zh-CN",
notifications: true
}
}
};
6. 实用类型工具:TypeScript 的"瑞士军刀"
TypeScript 提供了一系列实用类型,让类型操作变得轻松:
javascript
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
inStock: boolean;
}
// Partial - 所有属性变为可选
function updateProduct(product: IProduct, fieldsToUpdate: Partial<IProduct>) {
return { ...product, ...fieldsToUpdate };
}
// Readonly - 所有属性变为只读
const immutableProduct: Readonly<IProduct> = {
id: 1,
name: "Laptop",
price: 999,
inStock: true
};
// Pick - 选择特定属性
type ProductPreview = Pick<IProduct, "id" | "name" | "price">;
// Omit - 排除特定属性
type ProductWithoutId = Omit<IProduct, "id">;
7. 类型守卫与推断:TypeScript 的智能之处
TypeScript 能够根据代码逻辑自动推断类型,这让开发体验非常流畅:
javascript
// 类型守卫
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: string | number) {
if (isString(value)) {
// 在这个块中,TypeScript 知道 value 是 string
console.log(value.toUpperCase());
} else {
// 在这里,value 是 number
console.log(value.toFixed(2));
}
}
// 类型推断
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
]; // TypeScript 自动推断为 { name: string; age: number }[]
8. 实战经验:我踩过的类型坑与解决方案
坑1:过度使用 any
错误做法:
javascript
function processData(data: any): any {
// 这里完全失去了类型安全
return data.map(item => item.value);
}
正确做法:
javascript
function processData<T>(data: T[]): unknown[] {
// 至少保证输入是数组
return data.map(item => (item as any).value);
}
坑2:忽略 undefined 和 null
错误做法:
javascript
function getLength(str: string): number {
return str.length;
}
正确做法:
javascript
function getLength(str: string | null | undefined): number {
return str?.length || 0;
}
我的类型设计原则
- 渐进式严格:开始可以使用宽松类型,逐步收紧
- 语义化命名:让类型名自解释
- DRY 原则:避免重复的类型定义
- 适度抽象:不要过度设计类型系统
javascript
// 好的类型设计示例
type Email = string;
type UserID = number;
interface IBaseEntity {
id: UserID;
createdAt: Date;
updatedAt: Date;
}
interface IUser extends IBaseEntity {
email: Email;
name: string;
role: UserRole;
}
// 这样既清晰又易于维护
TypeScript 的类型系统就像给你的代码加上了一套智能导航系统,它不会限制你去哪里,但会确保你不会走错路。开始可能会觉得有些约束,但一旦习惯,你就会发现它带来的安全感和开发效率的提升是值得的。
希望这篇指南能帮你更好地理解 TypeScript 的类型系统!
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!