Zac Sokach•
TypeScript 实战心得:从踩坑到真香
0 分钟阅读0 字
分享三年 TypeScript 使用心得,从踩坑到真香的转变,包括实战经验、常见问题和解决方案。
分享三年 TypeScript 使用心得,从踩坑到真香的转变,包括实战经验、常见问题和解决方案。
相关文章推荐功能即将上线
三年前我开始用 TypeScript,说实话,一开始挺抗拒的。觉得类型定义太麻烦,代码量增加了不少。但现在?我已经离不开 TypeScript 了。今天想和大家分享一些实战心得,特别是那些我曾经踩过的坑。
刚开始我总纠结什么时候用 interface,什么时候用 type,后来发现其实没那么复杂:
// 我用 interface 的情况:对象类型、类的结构 interface User { id: number; name: string; email?: string; // 可选属性 readonly createdAt: Date; // 只读属性 } // 接口可以扩展,这点很棒 interface AdminUser extends User { permissions: string[]; } // 我用 type 的情况:联合类型、交叉类型、复杂类型 type Status = 'pending' | 'approved' | 'rejected'; type ID = string | number; type UserWithStatus = User & { status: Status };
我刚开始觉得泛型很难懂,后来发现其实就是"类型参数":
// 最简单的泛型函数 function identity<T>(arg: T): T { return arg; } // 实际项目中的例子 async function fetchApi<T>(url: string): Promise<T> { const response = await fetch(url); return response.json(); } // 使用时明确类型 const user = await fetchApi<User>('/api/user/1'); const posts = await fetchApi<Post[]>('/api/posts');
// 我常用的泛型约束 interface HasId { id: number; } function findById<T extends HasId>(items: T[], id: number): T | undefined { return items.find(item => item.id === id); } // 这样就能确保 T 一定有 id 属性 const user = findById(users, 1); // 类型安全
TypeScript 内置的工具类型真的很好用,我几乎每天都会用到:
interface User { id: number; name: string; email: string; password: string; createdAt: Date; } // Partial:所有属性变成可选的 function updateUser(id: number, updates: Partial<User>) { // 只需要传递要更新的字段 return { ...existingUser, ...updates }; } // Pick:只选择需要的属性 type UserSummary = Pick<User, 'id' | 'name' | 'email'>; // Omit:排除某些属性 type PublicUser = Omit<User, 'password'>; // Record:创建对象类型 type StatusMessages = Record<'success' | 'error', string>;
有时候内置的不够用,我会自己写:
// 把所有属性变成可选的,但保持嵌套结构 type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; }; // 提取数组元素的类型 type ArrayElement<T> = T extends (infer U)[] ? U : never; // 条件类型:根据条件选择类型 type NonNullable<T> = T extends null | undefined ? never : T;
有时候为了省事,我会想用 any,但后来发现这是饮鸩止渴:
// 错误做法:图省事用 any function processData(data: any): any { return data.map((item: any) => item.name); } // 正确做法:明确定义类型 interface DataItem { id: number; name: string; value: number; } function processData(data: DataItem[]): string[] { return data.map(item => item.name); }
我曾经滥用类型断言,结果导致运行时错误:
// 危险的做法 const user = response.data as User; // 更安全的做法 function isUser(obj: any): obj is User { return obj && typeof obj.id === 'number' && typeof obj.name === 'string' && typeof obj.email === 'string'; } const user = isUser(response.data) ? response.data : null;
经过多次调整,我的配置现在是这样的:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "baseUrl": ".", "paths": { "@/*": ["./src/*"], "@/components/*": ["./src/components/*"], "@/utils/*": ["./src/utils/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules"] }
我一开始怕严格模式太麻烦,但开启后真的避免了很多 bug:
有些库没有类型定义,我遇到过几种情况:
// 1. 使用 @types 包 npm install @types/lodash // 2. 自己声明类型 declare module 'some-library' { export function doSomething(): void; } // 3. 混合 JavaScript 和 TypeScript // 在 .d.ts 文件中声明全局类型 declare global { interface Window { someGlobalProperty: any; } }
用了三年 TypeScript,我觉得最大的价值是:
如果你刚开始用 TypeScript,我的建议是:
TypeScript 不是银弹,但它确实能让我的代码更健壮、更易维护。现在让我写纯 JavaScript 反而会觉得不安全了。
希望我的经验能帮到你,TypeScript 真香!