typescript-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript 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 mode for maximum type safety:
strictjson
{
"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 types from sneaking in
any
始终启用模式以获得最高级别的类型安全:
strictjson
{
"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 for:
interface- 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 for:
type- 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
anyunknown避免使用any
- 结合类型守卫使用unknown
anyunknowntypescript
// 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.tsBarrel 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 sparinglytypescript
// 不推荐 - 深层嵌套的映射类型会减慢编译速度
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 defaulttypescript
// 推荐 - 安全的属性访问
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
常见错误及规避方案
| Mistake | Problem | Solution |
|---|---|---|
Overusing | Defeats type checking | Use |
| Not using strict mode | Misses many errors | Enable |
| Redundant annotations | Clutters code | Trust type inference |
| Ignoring union types | Runtime errors | Use type guards |
| Not handling null | Crashes | Use |
| Nested conditionals | Hard to read | Use guard clauses |
| 错误 | 问题 | 解决方案 |
|---|---|---|
过度使用 | 失去类型检查能力 | 使用 |
| 未启用严格模式 | 遗漏大量错误 | 启用 |
| 冗余的类型注解 | 代码冗余杂乱 | 信任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();
// 主逻辑...