typescript-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Best Practices

TypeScript 最佳实践

Comprehensive guide to writing clean, type-safe, and maintainable TypeScript code.
本指南全面介绍如何编写整洁、类型安全且可维护的TypeScript代码。

When to Use

适用场景

  • Configuring a new TypeScript project
  • Deciding between interface vs type alias
  • Writing async/await code
  • Reviewing TypeScript code quality
  • Avoiding common TypeScript pitfalls
  • 配置新的TypeScript项目
  • 选择interface与type alias
  • 编写async/await代码
  • 评审TypeScript代码质量
  • 规避TypeScript常见陷阱

1. Project Configuration

1. 项目配置

Always enable
strict
mode for maximum type safety:
json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}
Why strict mode matters:
  • Catches bugs at compile time instead of runtime
  • Forces explicit handling of null/undefined
  • Prevents implicit
    any
    types from sneaking in
始终启用
strict
模式以获得最高级别的类型安全:
json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}
为什么严格模式很重要:
  • 在编译阶段而非运行阶段捕获bug
  • 强制显式处理null/undefined
  • 防止隐式
    any
    类型混入

2. Type System Best Practices

2. 类型系统最佳实践

Use Type Inference

使用类型推断

Let TypeScript infer types when obvious:
typescript
// Good - inference works fine
const name = 'Alice';
const count = 42;
const items = ['a', 'b', 'c'];

// Bad - redundant annotations
const name: string = 'Alice';
const count: number = 42;
当类型明显时,让TypeScript自动推断类型:
typescript
// 推荐 - 推断效果良好
const name = 'Alice';
const count = 42;
const items = ['a', 'b', 'c'];

// 不推荐 - 冗余的类型注解
const name: string = 'Alice';
const count: number = 42;

Be Explicit for Public APIs

公开API需显式声明类型

typescript
// Good - explicit for function signatures
function calculateTotal(items: CartItem[], taxRate: number): number {
  return items.reduce((sum, item) => sum + item.price, 0) * (1 + taxRate);
}

// Good - explicit for class properties
class UserService {
  private readonly cache: Map<string, User>;
  
  constructor(private api: ApiClient) {
    this.cache = new Map();
  }
}
typescript
// 推荐 - 函数签名清晰明确
function calculateTotal(items: CartItem[], taxRate: number): number {
  return items.reduce((sum, item) => sum + item.price, 0) * (1 + taxRate);
}

// 推荐 - 类属性显式声明
class UserService {
  private readonly cache: Map<string, User>;
  
  constructor(private api: ApiClient) {
    this.cache = new Map();
  }
}

Interface vs Type Alias

Interface与Type Alias的选择

Use
interface
for:
  • Object shapes that can be extended
  • Public API contracts
  • Declaration merging needs
typescript
interface User {
  id: string;
  name: string;
}

interface Admin extends User {
  permissions: string[];
}
Use
type
for:
  • Unions and intersections
  • Tuples
  • Mapped types
  • Primitive aliases
typescript
type Status = 'pending' | 'approved' | 'rejected';
type Point = [number, number];
type ReadonlyUser = Readonly<User>;
优先使用
interface
的场景:
  • 可扩展的对象结构
  • 公开API契约
  • 需要声明合并的情况
typescript
interface User {
  id: string;
  name: string;
}

interface Admin extends User {
  permissions: string[];
}
优先使用
type
的场景:
  • 联合类型与交叉类型
  • 元组
  • 映射类型
  • 原始类型别名
typescript
type Status = 'pending' | 'approved' | 'rejected';
type Point = [number, number];
type ReadonlyUser = Readonly<User>;

Avoid
any
- Use
unknown
with Type Guards

避免使用
any
- 结合类型守卫使用
unknown

typescript
// Bad - defeats type checking
function process(data: any) {
  return data.toUpperCase(); // No error, but might crash
}

// Good - use unknown with type guards
function process(data: unknown): string {
  if (typeof data === 'string') {
    return data.toUpperCase();
  }
  throw new Error('Expected string');
}

// Good - use generics for flexibility
function identity<T>(value: T): T {
  return value;
}
typescript
// 不推荐 - 失去类型检查能力
function process(data: any) {
  return data.toUpperCase(); // 无编译错误,但运行时可能崩溃
}

// 推荐 - 使用unknown并配合类型守卫
function process(data: unknown): string {
  if (typeof data === 'string') {
    return data.toUpperCase();
  }
  throw new Error('Expected string');
}

// 推荐 - 使用泛型保证灵活性
function identity<T>(value: T): T {
  return value;
}

3. Code Organization

3. 代码组织

File Naming Convention

文件命名规范

Use lowercase with dots for clarity:
src/
├── user/
│   ├── user.service.ts
│   ├── user.model.ts
│   ├── user.controller.ts
│   └── index.ts          # Barrel file
├── auth/
│   ├── auth.service.ts
│   └── index.ts
└── types/
    └── index.ts
使用小写加圆点的命名方式以提升清晰度:
src/
├── user/
│   ├── user.service.ts
│   ├── user.model.ts
│   ├── user.controller.ts
│   └── index.ts          # 桶文件
├── auth/
│   ├── auth.service.ts
│   └── index.ts
└── types/
    └── index.ts

Barrel Files for Clean Exports

使用桶文件实现清晰导出

typescript
// user/index.ts
export { UserService } from './user.service';
export { User, CreateUserDto } from './user.model';
export { UserController } from './user.controller';
typescript
// Consumer imports cleanly
import { UserService, User } from './user';
typescript
// user/index.ts
export { UserService } from './user.service';
export { User, CreateUserDto } from './user.model';
export { UserController } from './user.controller';
typescript
// 使用者可以清晰地导入
import { UserService, User } from './user';

4. Functions Best Practices

4. 函数最佳实践

Explicit Parameter Types

显式声明参数类型

typescript
// Good - clear contract
function greet(name: string, greeting = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

// Good - use rest parameters for variable arguments
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}
typescript
// 推荐 - 契约清晰
function greet(name: string, greeting = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

// 推荐 - 使用剩余参数处理可变参数
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

Single Responsibility

单一职责原则

typescript
// Bad - function does too much
function processUser(user: User) {
  // validates, transforms, saves, and sends email
}

// Good - split into focused functions
function validateUser(user: User): ValidationResult { ... }
function transformUser(user: User): TransformedUser { ... }
function saveUser(user: TransformedUser): Promise<void> { ... }
function sendWelcomeEmail(user: User): Promise<void> { ... }
typescript
// 不推荐 - 函数职责过多
function processUser(user: User) {
  // 同时负责验证、转换、保存和发送邮件
}

// 推荐 - 拆分为多个专注的函数
function validateUser(user: User): ValidationResult { ... }
function transformUser(user: User): TransformedUser { ... }
function saveUser(user: TransformedUser): Promise<void> { ... }
function sendWelcomeEmail(user: User): Promise<void> { ... }

Guard Clauses for Early Returns

使用卫语句提前返回

typescript
// Good - guard clauses
function processOrder(order: Order | null): ProcessedOrder {
  if (!order) throw new Error('Order required');
  if (order.items.length === 0) throw new Error('Order must have items');
  if (order.status !== 'pending') throw new Error('Order already processed');
  
  // Main logic here - no nesting
  return { ...order, status: 'processed' };
}
typescript
// 推荐 - 卫语句
function processOrder(order: Order | null): ProcessedOrder {
  if (!order) throw new Error('Order required');
  if (order.items.length === 0) throw new Error('Order must have items');
  if (order.status !== 'pending') throw new Error('Order already processed');
  
  // 主逻辑无需嵌套
  return { ...order, status: 'processed' };
}

5. Async/Await Patterns

5. Async/Await模式

Always Handle Errors

始终处理错误

typescript
// Good - explicit error handling
async function fetchUser(id: string): Promise<User> {
  try {
    const response = await api.get(`/users/${id}`);
    return response.data;
  } catch (error) {
    if (error instanceof NotFoundError) {
      throw new UserNotFoundError(id);
    }
    throw error;
  }
}
typescript
// 推荐 - 显式错误处理
async function fetchUser(id: string): Promise<User> {
  try {
    const response = await api.get(`/users/${id}`);
    return response.data;
  } catch (error) {
    if (error instanceof NotFoundError) {
      throw new UserNotFoundError(id);
    }
    throw error;
  }
}

Use Promise.all for Parallel Operations

使用Promise.all实现并行操作

typescript
// Bad - sequential when parallel is possible
const user = await fetchUser(id);
const orders = await fetchOrders(id);
const preferences = await fetchPreferences(id);

// Good - parallel execution
const [user, orders, preferences] = await Promise.all([
  fetchUser(id),
  fetchOrders(id),
  fetchPreferences(id),
]);
typescript
// 不推荐 - 本可并行却串行执行
const user = await fetchUser(id);
const orders = await fetchOrders(id);
const preferences = await fetchPreferences(id);

// 推荐 - 并行执行
const [user, orders, preferences] = await Promise.all([
  fetchUser(id),
  fetchOrders(id),
  fetchPreferences(id),
]);

Flatten Async Chains

扁平化异步调用链

typescript
// Bad - callback hell with async
async function bad() {
  return fetchUser().then(user => {
    return fetchOrders(user.id).then(orders => {
      return processOrders(orders).then(result => {
        return result;
      });
    });
  });
}

// Good - flat async/await
async function good() {
  const user = await fetchUser();
  const orders = await fetchOrders(user.id);
  return processOrders(orders);
}
typescript
// 不推荐 - 异步回调地狱
async function bad() {
  return fetchUser().then(user => {
    return fetchOrders(user.id).then(orders => {
      return processOrders(orders).then(result => {
        return result;
      });
    });
  });
}

// 推荐 - 扁平化async/await
async function good() {
  const user = await fetchUser();
  const orders = await fetchOrders(user.id);
  return processOrders(orders);
}

6. Testing and Quality

6. 测试与质量保障

Dependency Injection for Testability

依赖注入提升可测试性

typescript
interface PaymentGateway {
  charge(amount: number): Promise<boolean>;
}

class PaymentProcessor {
  constructor(private gateway: PaymentGateway) {}
  
  async processPayment(amount: number): Promise<boolean> {
    if (amount <= 0) throw new Error('Amount must be positive');
    return this.gateway.charge(amount);
  }
}

// Easy to test with mock
const mockGateway: PaymentGateway = {
  charge: jest.fn().mockResolvedValue(true),
};
const processor = new PaymentProcessor(mockGateway);
typescript
interface PaymentGateway {
  charge(amount: number): Promise<boolean>;
}

class PaymentProcessor {
  constructor(private gateway: PaymentGateway) {}
  
  async processPayment(amount: number): Promise<boolean> {
    if (amount <= 0) throw new Error('Amount must be positive');
    return this.gateway.charge(amount);
  }
}

// 使用模拟对象轻松测试
const mockGateway: PaymentGateway = {
  charge: jest.fn().mockResolvedValue(true),
};
const processor = new PaymentProcessor(mockGateway);

Type Guards for Runtime Checking

使用类型守卫进行运行时检查

typescript
interface Cat {
  meow(): void;
}

interface Dog {
  bark(): void;
}

function isCat(pet: Cat | Dog): pet is Cat {
  return 'meow' in pet;
}

function makeSound(pet: Cat | Dog) {
  if (isCat(pet)) {
    pet.meow(); // TypeScript knows it's Cat
  } else {
    pet.bark(); // TypeScript knows it's Dog
  }
}
typescript
interface Cat {
  meow(): void;
}

interface Dog {
  bark(): void;
}

function isCat(pet: Cat | Dog): pet is Cat {
  return 'meow' in pet;
}

function makeSound(pet: Cat | Dog) {
  if (isCat(pet)) {
    pet.meow(); // TypeScript知道这是Cat类型
  } else {
    pet.bark(); // TypeScript知道这是Dog类型
  }
}

7. Performance Considerations

7. 性能考量

Type-Only Imports

类型仅导入

typescript
// Good - type stripped at compile time, better tree-shaking
import type { User, Order } from './types';
import { fetchUser } from './api';

// Also good for re-exports
export type { User, Order };
typescript
// 推荐 - 类型在编译时被移除,优化摇树优化
import type { User, Order } from './types';
import { fetchUser } from './api';

// 重导出类型时也适用
export type { User, Order };

Const Assertions for Literal Types

使用const断言获取字面量类型

typescript
// Creates readonly tuple with literal types
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // "red" | "green" | "blue"

// Works for objects too
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
} as const;
typescript
// 创建只读元组并保留字面量类型
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // "red" | "green" | "blue"

// 对象同样适用
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
} as const;

Avoid Excessive Type Complexity

避免过度复杂的类型

typescript
// Bad - deeply nested mapped types slow compilation
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

// Consider simpler alternatives or use sparingly
typescript
// 不推荐 - 深层嵌套的映射类型会减慢编译速度
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

// 考虑使用更简单的替代方案,或谨慎使用

8. Handle Null/Undefined Properly

8. 正确处理Null/Undefined

Optional Chaining and Nullish Coalescing

可选链与空值合并运算符

typescript
// Good - safe property access
function getLength(str: string | null): number {
  return str?.length ?? 0;
}

// Good - safe method calls
const result = user?.getProfile?.()?.name ?? 'Anonymous';

// Good - default values only for null/undefined
const port = config.port ?? 3000; // 0 is valid, won't use default
typescript
// 推荐 - 安全的属性访问
function getLength(str: string | null): number {
  return str?.length ?? 0;
}

// 推荐 - 安全的方法调用
const result = user?.getProfile?.()?.name ?? 'Anonymous';

// 推荐 - 仅在null/undefined时使用默认值
const port = config.port ?? 3000; // 0是有效值,不会使用默认值

Discriminated Unions for State

使用可辨识联合管理状态

typescript
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function renderState<T>(state: AsyncState<T>) {
  switch (state.status) {
    case 'idle':
      return 'Ready';
    case 'loading':
      return 'Loading...';
    case 'success':
      return state.data; // TypeScript knows data exists
    case 'error':
      return state.error.message; // TypeScript knows error exists
  }
}
typescript
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function renderState<T>(state: AsyncState<T>) {
  switch (state.status) {
    case 'idle':
      return '就绪';
    case 'loading':
      return '加载中...';
    case 'success':
      return state.data; // TypeScript知道data存在
    case 'error':
      return state.error.message; // TypeScript知道error存在
  }
}

Common Mistakes to Avoid

常见错误及规避方案

MistakeProblemSolution
Overusing
any
Defeats type checkingUse
unknown
, generics, or proper types
Not using strict modeMisses many errorsEnable
"strict": true
Redundant annotationsClutters codeTrust type inference
Ignoring union typesRuntime errorsUse type guards
Not handling nullCrashesUse
?.
and
??
operators
Nested conditionalsHard to readUse guard clauses
错误问题解决方案
过度使用
any
失去类型检查能力使用
unknown
、泛型或正确的类型
未启用严格模式遗漏大量错误启用
"strict": true
冗余的类型注解代码冗余杂乱信任TypeScript的类型推断
忽略联合类型运行时错误使用类型守卫
未处理空值运行时崩溃使用
?.
??
运算符
嵌套条件语句代码可读性差使用卫语句

Quick Reference

快速参考

typescript
// Type inference - let TS do the work
const name = 'Alice';

// Explicit for APIs
function greet(name: string): string { ... }

// Unknown over any
function safe(data: unknown) { ... }

// Type-only imports
import type { User } from './types';

// Const assertions
const tuple = [1, 2] as const;

// Null safety
const len = str?.length ?? 0;

// Guard clauses
if (!valid) throw new Error();
// main logic...
typescript
// 类型推断 - 让TS自动处理
const name = 'Alice';

// 公开API显式声明
function greet(name: string): string { ... }

// 用unknown替代any
function safe(data: unknown) { ... }

// 类型仅导入
import type { User } from './types';

// Const断言
const tuple = [1, 2] as const;

// 空值安全
const len = str?.length ?? 0;

// 卫语句
if (!valid) throw new Error();
// 主逻辑...

References

参考资料