模块最佳实践
TypeScript 的模块系统为代码组织提供了强大的工具。合理使用模块能显著提升项目的可维护性和可扩展性。下面从多个维度探讨模块化的核心实践方式。
模块划分原则
模块划分应遵循高内聚低耦合原则。一个典型错误是将完全不相关的功能塞进同一个模块:
// 错误示例:工具函数与业务逻辑混合
export function formatDate(date: Date): string {
return date.toISOString()
}
export class UserService {
async login() { /*...*/ }
}
改进方案应按功能边界拆分:
// utils/date.ts
export function formatDate(date: Date): string {
return date.toISOString()
}
// services/user.ts
export class UserService {
async login() { /*...*/ }
}
导出策略优化
避免使用默认导出(default export),原因有三:
- 重命名时容易产生歧义
- 自动导入工具难以准确识别
- 重构时容易破坏引用
推荐使用命名导出:
// 推荐写法
export const API_TIMEOUT = 5000
export interface UserProfile { /*...*/ }
export function fetchUser() { /*...*/ }
// 使用时明确引用路径
import { API_TIMEOUT, fetchUser } from './api'
循环依赖处理
TypeScript 对循环依赖的检测有限,应通过以下方式避免:
- 提取公共类型到独立模块
- 使用依赖注入模式
- 延迟加载(Lazy import)
// 解决方案示例:依赖注入
class AuthService {
constructor(private userService: UserService) {}
}
class UserService {
private authService?: AuthService
setAuthService(service: AuthService) {
this.authService = service
}
}
路径别名配置
在大型项目中,相对路径会导致引用混乱。配置 tsconfig.json
:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@utils/*": ["utils/*"],
"@models/*": ["models/*"]
}
}
}
使用示例:
import { formatDate } from '@utils/date'
import type { User } from '@models/user'
动态导入实践
按需加载模块能显著提升应用性能:
// 动态加载富文本编辑器
async function loadEditor() {
const { MarkdownEditor } = await import('@components/editor')
return MarkdownEditor
}
// 配合React Suspense使用
const Editor = React.lazy(() => import('@components/editor'))
类型导出规范
模块应显式导出类型定义,避免隐式类型泄露:
// 正确做法:显式导出类型
export interface Config {
env: 'dev' | 'prod'
debug: boolean
}
export function initApp(config: Config) { /*...*/ }
// 错误做法:参数类型隐式暴露
export function initApp(config: { env: string }) { /*...*/ }
模块测试策略
为模块编写测试时应保持独立:
// 测试示例使用Jest
import { formatDate } from '@utils/date'
describe('date utils', () => {
it('should format ISO string', () => {
const date = new Date('2023-01-01')
expect(formatDate(date)).toMatch(/2023-01-01/)
})
})
模块文档规范
使用 JSDoc 增强类型提示:
/**
* 用户权限验证模块
* @module services/auth
*/
/**
* 检查当前用户权限
* @param {string} permission - 权限代码
* @returns {Promise<boolean>} 是否有权限
*/
export async function checkPermission(permission: string): Promise<boolean> {
// ...
}
模块版本管理
对公共模块应进行版本控制:
- 使用
changesets
管理变更日志 - 遵循语义化版本规范
- 私有模块使用
npm link
开发
# 典型工作流
npx changeset add
npx changeset version
npm publish
模块性能优化
通过打包分析工具检测模块体积:
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true
})
]
})
模块热更新配置
开发环境优化HMR体验:
// 自定义HMR逻辑
if (import.meta.hot) {
import.meta.hot.accept('./module', (newModule) => {
newModule?.applyUpdate()
})
}
跨平台模块设计
设计可复用模块时应考虑多平台支持:
// 平台抽象层设计
export abstract class Storage {
abstract getItem(key: string): string | null
}
// Web实现
export class WebStorage extends Storage {
getItem(key: string) {
return localStorage.getItem(key)
}
}
// Node实现
export class ServerStorage extends Storage {
getItem(key: string) {
return process.env[key] ?? null
}
}
模块错误处理
统一错误处理机制示例:
// error.ts 模块
export class AppError extends Error {
constructor(
public readonly code: string,
message?: string
) {
super(message)
}
}
export function handleError(error: unknown) {
if (error instanceof AppError) {
console.error(`[${error.code}]`, error.message)
} else {
console.error('Unknown error', error)
}
}
模块生命周期管理
需要初始化的模块应提供明确的生命周期方法:
// db.ts 模块
let connection: DatabaseConnection | null = null
export async function connectDB(config: DBConfig) {
if (connection) return connection
connection = await createConnection(config)
return connection
}
export async function disconnectDB() {
if (connection) {
await connection.close()
connection = null
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn