(Updated: 2024年9月25日
)
TypeScript 实战心得:从踩坑到真香
#TypeScript
#JavaScript
#编程
三年前我开始用 TypeScript,说实话,一开始挺抗拒的。觉得类型定义太麻烦,代码量增加了不少。但现在?我已经离不开 TypeScript 了。今天想和大家分享一些实战心得,特别是那些我曾经踩过的坑。
类型定义:interface vs type,我这样选
曾经的困惑
刚开始我总纠结什么时候用 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 };
我的实用经验
- 对象结构用 interface:更直观,支持扩展
- 联合类型用 type:比如状态码、配置选项
- 复杂类型用 type:比如工具类型、条件类型
泛型:别被吓到,其实很实用
从简单到复杂
我刚开始觉得泛型很难懂,后来发现其实就是”类型参数”:
// 最简单的泛型函数
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 的瑞士军刀
我最常用的几个
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,但后来发现这是饮鸩止渴:
// 错误做法:图省事用 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;
项目中的 TypeScript 配置
我的 tsconfig.json
经过多次调整,我的配置现在是这样的:
{
"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:
- noImplicitAny:防止意外的 any 类型
- strictNullChecks:避免空值错误
- noImplicitReturns:确保所有分支都有返回值
与第三方库打交道
类型定义的坑
有些库没有类型定义,我遇到过几种情况:
// 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,我觉得最大的价值是:
- 减少 bug:类型检查在编译时就能发现很多问题
- 更好的 IDE 支持:自动补全、重构更可靠
- 代码即文档:类型定义就是最好的文档
- 重构更安全:改个函数名,所有调用处都会报错
给新手的建议
如果你刚开始用 TypeScript,我的建议是:
- 别怕麻烦:一开始会觉得类型定义很烦,坚持下来就好了
- 开启严格模式:虽然一开始会有很多报错,但长期看很值
- 多用工具类型:别重复造轮子
- 逐步迁移:如果是老项目,可以逐步添加类型
TypeScript 不是银弹,但它确实能让我的代码更健壮、更易维护。现在让我写纯 JavaScript 反而会觉得不安全了。
希望我的经验能帮到你,TypeScript 真香!