TypeScript 实战:常见模式与最佳实践

这一篇总结 TypeScript 实战中的常见模式、最佳实践,
以及常见错误和解决方案。

 1class Database {
 2  private static instance: Database;
 3
 4  private constructor() {
 5    // 私有构造函数
 6  }
 7
 8  static getInstance(): Database {
 9    if (!Database.instance) {
10      Database.instance = new Database();
11    }
12    return Database.instance;
13  }
14
15  connect(): void {
16    console.log('Connected to database');
17  }
18}
19
20// 使用
21const db1 = Database.getInstance();
22const db2 = Database.getInstance();
23console.log(db1 === db2); // true
 1interface Product {
 2  name: string;
 3  price: number;
 4}
 5
 6class Book implements Product {
 7  constructor(
 8    public name: string,
 9    public price: number
10  ) {}
11}
12
13class Electronics implements Product {
14  constructor(
15    public name: string,
16    public price: number
17  ) {}
18}
19
20class ProductFactory {
21  static create(type: 'book' | 'electronics', name: string, price: number): Product {
22    switch (type) {
23      case 'book':
24        return new Book(name, price);
25      case 'electronics':
26        return new Electronics(name, price);
27      default:
28        throw new Error(`Unknown product type: ${type}`);
29    }
30  }
31}
32
33// 使用
34const book = ProductFactory.create('book', 'TypeScript Guide', 29.99);
35const laptop = ProductFactory.create('electronics', 'Laptop', 999.99);
 1interface PaymentStrategy {
 2  pay(amount: number): void;
 3}
 4
 5class CreditCardPayment implements PaymentStrategy {
 6  pay(amount: number): void {
 7    console.log(`Paying ${amount} with credit card`);
 8  }
 9}
10
11class PayPalPayment implements PaymentStrategy {
12  pay(amount: number): void {
13    console.log(`Paying ${amount} with PayPal`);
14  }
15}
16
17class PaymentProcessor {
18  constructor(private strategy: PaymentStrategy) {}
19
20  processPayment(amount: number): void {
21    this.strategy.pay(amount);
22  }
23
24  setStrategy(strategy: PaymentStrategy): void {
25    this.strategy = strategy;
26  }
27}
28
29// 使用
30const processor = new PaymentProcessor(new CreditCardPayment());
31processor.processPayment(100);
32processor.setStrategy(new PayPalPayment());
33processor.processPayment(200);
 1type Observer<T> = (data: T) => void;
 2
 3class Observable<T> {
 4  private observers: Observer<T>[] = [];
 5
 6  subscribe(observer: Observer<T>): () => void {
 7    this.observers.push(observer);
 8    return () => {
 9      this.observers = this.observers.filter(o => o !== observer);
10    };
11  }
12
13  notify(data: T): void {
14    this.observers.forEach(observer => observer(data));
15  }
16}
17
18// 使用
19const observable = new Observable<string>();
20const unsubscribe = observable.subscribe(data => {
21  console.log('Received:', data);
22});
23
24observable.notify('Hello');
25unsubscribe();
 1type ApiResponse<T> = 
 2  | { success: true; data: T }
 3  | { success: false; error: string };
 4
 5async function fetchUser(id: string): Promise<ApiResponse<User>> {
 6  try {
 7    const response = await fetch(`/api/users/${id}`);
 8    if (!response.ok) {
 9      return { success: false, error: `HTTP ${response.status}` };
10    }
11    const data = await response.json();
12    return { success: true, data };
13  } catch (error) {
14    return { success: false, error: error.message };
15  }
16}
17
18// 使用
19const result = await fetchUser('123');
20if (result.success) {
21  console.log(result.data); // TypeScript 知道这里有 data
22} else {
23  console.error(result.error); // TypeScript 知道这里有 error
24}
 1type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
 2
 3interface RequestConfig {
 4  method: HttpMethod;
 5  url: string;
 6  data?: any;
 7  headers?: Record<string, string>;
 8}
 9
10class HttpClient {
11  async request<T>(config: RequestConfig): Promise<T> {
12    const response = await fetch(config.url, {
13      method: config.method,
14      headers: config.headers,
15      body: config.data ? JSON.stringify(config.data) : undefined
16    });
17    
18    if (!response.ok) {
19      throw new Error(`HTTP ${response.status}`);
20    }
21    
22    return response.json();
23  }
24
25  get<T>(url: string): Promise<T> {
26    return this.request<T>({ method: 'GET', url });
27  }
28
29  post<T>(url: string, data: any): Promise<T> {
30    return this.request<T>({ method: 'POST', url, data });
31  }
32}
33
34// 使用
35const client = new HttpClient();
36const user = await client.get<User>('/api/users/123');
37const newUser = await client.post<User>('/api/users', { name: 'John' });
 1type Result<T, E = Error> = 
 2  | { success: true; data: T }
 3  | { success: false; error: E };
 4
 5function divide(a: number, b: number): Result<number, string> {
 6  if (b === 0) {
 7    return { success: false, error: 'Division by zero' };
 8  }
 9  return { success: true, data: a / b };
10}
11
12// 使用
13const result = divide(10, 2);
14if (result.success) {
15  console.log(result.data);
16} else {
17  console.error(result.error);
18}
 1type Option<T> = T | null;
 2
 3function findUser(id: string): Option<User> {
 4  // 可能返回 null
 5  return users.find(u => u.id === id) || null;
 6}
 7
 8// 使用
 9const user = findUser('123');
10if (user) {
11  console.log(user.name);
12}
 1// 不好的做法
 2const value = getValue() as string;
 3value.toUpperCase();
 4
 5// 好的做法:使用类型守卫
 6function isString(value: unknown): value is string {
 7  return typeof value === 'string';
 8}
 9
10const value = getValue();
11if (isString(value)) {
12  value.toUpperCase();
13}
 1// 不好的做法
 2function process(data: any) {
 3  return data.value;
 4}
 5
 6// 好的做法:使用泛型或 unknown
 7function process<T extends { value: unknown }>(data: T) {
 8  return data.value;
 9}
10
11// 或者使用 unknown
12function process(data: unknown) {
13  if (typeof data === 'object' && data !== null && 'value' in data) {
14    return (data as { value: unknown }).value;
15  }
16  throw new Error('Invalid data');
17}
 1// 不好的做法
 2function getLength(str: string | null): number {
 3  return str.length; // 可能为 null
 4}
 5
 6// 好的做法:使用类型收窄
 7function getLength(str: string | null): number {
 8  if (str === null) {
 9    return 0;
10  }
11  return str.length;
12}
13
14// 或者使用可选链
15function getLength(str: string | null): number {
16  return str?.length ?? 0;
17}
1// 不好的做法
2function process(data: any[]) {
3  return data.map(item => item.value);
4}
5
6// 好的做法:使用泛型
7function process<T extends { value: unknown }>(data: T[]): T['value'][] {
8  return data.map(item => item.value);
9}
1// 不好的做法
2function calculate(a: number, b: number) {
3  return a + b;
4}
5
6// 好的做法:明确返回类型
7function calculate(a: number, b: number): number {
8  return a + b;
9}
1{
2  "compilerOptions": {
3    "strict": true
4  }
5}
1// 接口支持声明合并
2interface Config {
3  api: string;
4}
5
6interface Config {
7  timeout: number;
8}
1// 好的做法
2function isUser(value: unknown): value is User {
3  return (
4    typeof value === 'object' &&
5    value !== null &&
6    'id' in value &&
7    'name' in value
8  );
9}
1const config = {
2  api: 'https://api.example.com',
3  timeout: 5000
4} as const;
1// 使用 Partial 而不是手动定义
2type UpdateUser = Partial<User>;
1// 好的泛型使用
2function identity<T>(value: T): T {
3  return value;
4}
5
6// 避免过度泛型化
7function process<T, U, V, W>(a: T, b: U, c: V): W {
8  // 太复杂了
9}
1type Result<T> = 
2  | { type: 'success'; data: T }
3  | { type: 'error'; message: string };
1// 不好的做法
2const value = data as any as string as number;
3
4// 好的做法:使用类型守卫或重新设计类型
1// 只导入类型,不导入值
2import type { User } from './types';
3import { type User, type Admin } from './types';
1{
2  "compilerOptions": {
3    "incremental": true
4  }
5}
1{
2  "compilerOptions": {
3    "composite": true
4  },
5  "references": [
6    { "path": "./packages/core" }
7  ]
8}

TypeScript 实战中的要点:

  • 设计模式:单例、工厂、策略、观察者等
  • 类型安全的 API:Result 类型、类型安全的 HTTP 客户端
  • 错误处理:Result 类型、Option 类型
  • 常见错误:避免过度使用类型断言、any、忽略 null/undefined
  • 最佳实践:严格模式、类型守卫、工具类型、可辨识联合
  • 性能优化:类型导入、增量编译、项目引用

在实际开发中:

  • 遵循最佳实践,写出类型安全的代码
  • 避免常见错误,提高代码质量
  • 使用设计模式组织代码结构
  • 优化编译性能,提高开发效率