模块化设计的最佳实践有哪些?

1. 单一职责原则

每个模块只做一件事。

// ❌ 混合职责
// user.js
export function fetchUser() { ... }
export function formatUser() { ... }
export function validateUser() { ... }
export function sendEmail() { ... }

// ✅ 拆分
// userApi.js → fetchUser
// userFormat.js → formatUser
// userValidate.js → validateUser
// emailService.js → sendEmail

2. 显式依赖

// ❌ 隐式依赖(全局变量)
function getUser() {
  return fetch(API_URL)  // API_URL 从哪来?
}

// ✅ 显式导入
import { API_URL } from './config.js'
function getUser() {
  return fetch(API_URL)
}

3. 最小导出

只导出外部需要的 API,内部实现保持私有。

// ✅ 使用闭包或 # 私有字段
export class Stack {
  #items = []  // 私有
  
  push(item) { this.#items.push(item) }
  pop() { return this.#items.pop() }
  get size() { return this.#items.length }
}

4. 统一导出入口

// utils/index.js(barrel file)
export { formatDate } from './date.js'
export { formatCurrency } from './currency.js'
export { debounce, throttle } from './function.js'

// 使用方只需一个 import
import { formatDate, debounce } from './utils'

⚠️ Barrel file 可能影响 Tree Shaking,打包工具需要配置好。

5. 避免副作用

// ❌ 模块导入时有副作用
// global.js
window.myConfig = { ... }
document.title = 'My App'

// ✅ 显式调用
// global.js
export function init() {
  window.myConfig = { ... }
}

// app.js
import { init } from './global.js'
init()  // 显式调用

6. 接口稳定

// ✅ 接口设计稳定、向后兼容
export function createLogger(options) {
  return {
    log: (msg) => { ... },
    error: (msg) => { ... },
    warn: (msg) => { ... }
  }
}

// ❌ 每次升级都改 API
// v1: export function log(msg) {}
// v2: export function log(level, msg) {}  // 破坏性变更

7. 按功能分目录

src/
├── api/            # API 请求
│   ├── user.js
│   └── order.js
├── components/     # UI 组件
│   ├── Button.vue
│   └── Input.vue
├── composables/    # 组合式函数
│   ├── useUser.js
│   └── useCart.js
├── utils/          # 工具函数
├── stores/         # 状态管理
├── types/          # TypeScript 类型
└── views/          # 页面

8. 类型优先

// 定义清晰的接口
export interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user' | 'guest'
}

export interface UserService {
  getUser(id: number): Promise<User>
  updateUser(id: number, data: Partial<User>): Promise<User>
}

9. 依赖方向单一

// ✅ 单向依赖
views → composables → api → utils

// ❌ 循环依赖
api → utils → api

10. 可测试性

// ✅ 依赖注入,方便测试
export function createUserService(apiClient) {
  return {
    getUser: (id) => apiClient.get(`/users/${id}`)
  }
}

// 测试时可以注入 mock
const mockApi = { get: jest.fn().mockResolvedValue({ id: 1, name: 'Tom' }) }
const service = createUserService(mockApi)

11. 文件命名规范

组件:PascalCase(Button.vue, UserList.vue)
工具:camelCase(formatDate.js, useUser.js)
常量:UPPER_SNAKE_CASE(API_CONFIG.js)
类型:PascalCase(User.ts, ApiResponse.ts)

12. 合理使用默认导出和命名导出

// 组件/类 → 默认导出
export default class User { }

// 工具函数/常量 → 命名导出
export function formatDate() { }
export const API_URL = '/api'

// 混用时保持一致
export function helper() { }
export default function main() { }

总结

好的模块化设计 = 高内聚 + 低耦合 + 单一职责 + 显式依赖

同分类其他题目