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();
类型安全的 API 调用
API 响应类型
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}
类型安全的 HTTP 客户端
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' });
错误处理模式
Result 类型
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}
Option 类型
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:类型断言过度使用
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}
错误 2:使用 any
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}
错误 3:忽略 null/undefined
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}
错误 4:函数参数类型不明确
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}
错误 5:忽略返回类型
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. 使用严格模式
1{
2 "compilerOptions": {
3 "strict": true
4 }
5}
2. 优先使用接口而不是类型别名(当可能时)
1// 接口支持声明合并
2interface Config {
3 api: string;
4}
5
6interface Config {
7 timeout: number;
8}
3. 使用类型守卫而不是类型断言
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}
4. 使用 const 断言获得更精确的类型
1const config = {
2 api: 'https://api.example.com',
3 timeout: 5000
4} as const;
5. 使用工具类型减少重复
1// 使用 Partial 而不是手动定义
2type UpdateUser = Partial<User>;
6. 合理使用泛型
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}
7. 使用可辨识联合
1type Result<T> =
2 | { type: 'success'; data: T }
3 | { type: 'error'; message: string };
8. 避免类型断言链
1// 不好的做法
2const value = data as any as string as number;
3
4// 好的做法:使用类型守卫或重新设计类型
性能优化
1. 使用类型导入
1// 只导入类型,不导入值
2import type { User } from './types';
3import { type User, type Admin } from './types';
2. 启用增量编译
1{
2 "compilerOptions": {
3 "incremental": true
4 }
5}
3. 使用项目引用
1{
2 "compilerOptions": {
3 "composite": true
4 },
5 "references": [
6 { "path": "./packages/core" }
7 ]
8}
小结
TypeScript 实战中的要点:
- 设计模式:单例、工厂、策略、观察者等
- 类型安全的 API:Result 类型、类型安全的 HTTP 客户端
- 错误处理:Result 类型、Option 类型
- 常见错误:避免过度使用类型断言、any、忽略 null/undefined
- 最佳实践:严格模式、类型守卫、工具类型、可辨识联合
- 性能优化:类型导入、增量编译、项目引用
在实际开发中:
- 遵循最佳实践,写出类型安全的代码
- 避免常见错误,提高代码质量
- 使用设计模式组织代码结构
- 优化编译性能,提高开发效率