ZACSOKACH
(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,我觉得最大的价值是:

  1. 减少 bug:类型检查在编译时就能发现很多问题
  2. 更好的 IDE 支持:自动补全、重构更可靠
  3. 代码即文档:类型定义就是最好的文档
  4. 重构更安全:改个函数名,所有调用处都会报错

给新手的建议

如果你刚开始用 TypeScript,我的建议是:

  1. 别怕麻烦:一开始会觉得类型定义很烦,坚持下来就好了
  2. 开启严格模式:虽然一开始会有很多报错,但长期看很值
  3. 多用工具类型:别重复造轮子
  4. 逐步迁移:如果是老项目,可以逐步添加类型

TypeScript 不是银弹,但它确实能让我的代码更健壮、更易维护。现在让我写纯 JavaScript 反而会觉得不安全了。

希望我的经验能帮到你,TypeScript 真香!