模块基础与ES模块规范

在现代JavaScript/TypeScript开发中,模块系统是实现代码组织、复用与维护的核心机制。通过将代码分割为独立模块,开发者可以实现关注点分离,提升代码的可扩展性与团队协作效率。ES模块(ESM)作为官方标准,已成为TypeScript项目的主流模块方案,其核心价值在于提供了标准化的导出(Export)与导入(Import)语法,支持类型与值的精细化管理。

命名导出与导入

命名导出允许模块对外暴露多个独立成员,每个成员需显式指定名称。其语法形式包括声明时导出与集中导出两种:

typescript
复制代码
// 声明时导出
export const framework = "TypeScript";
export function log(message: string): void {
  console.log(`[TS] ${message}`);
}

// 集中导出(适用于已有声明的成员)
const version = "5.0";
export { version };

对应的导入需使用解构语法指定成员名称,且名称必须与导出端完全一致:

typescript
复制代码
import { framework, log, version } from './constants';
log(`${framework} ${version}`); // 输出 "[TS] TypeScript 5.0"

默认导出与导入

默认导出用于模块对外暴露单一主要成员,每个模块仅允许一个默认导出。其语法简洁,导入时可自定义成员名称:

typescript
复制代码
// User.ts - 默认导出类
export default class User {
  constructor(public name: string) {}
}

// 导入时自定义名称(通常保持与类名一致以增强可读性)
import User from './User';
const user = new User("Alice");

默认导出适用于工具类、组件等具有明确单一职责的模块,但需注意过度使用可能导致导入名称混乱,建议与命名导出配合使用以平衡灵活性与可读性。

类型导出(TypeScript 5.0+)

TypeScript 5.0引入了显式类型导出机制,通过export type语法明确区分类型与值的导出。这一特性解决了传统导出中类型与值的歧义问题,且类型导出在编译为JavaScript时会被完全移除,避免运行时冗余。

typescript
复制代码
// types.ts - 定义接口类型
interface User {
  id: number;
  name: string;
}

// 导出类型(仅用于类型系统)
export type { User };

// 导出值(运行时保留)
export const DEFAULT_USER: User = { id: 0, name: "Guest" };

类型导出与普通导出的核心区别在于:

  • export type仅用于类型(接口、类型别名、类类型等),编译后无残留
  • 普通export可导出值(变量、函数、类实例等),编译后保留在运行时代码中

此外,TypeScript支持通过export type * from './types'批量导出其他模块的类型,实现类型的聚合管理:

typescript
复制代码
// index.ts - 汇总类型导出
export type * from './user.types';
export type * from './post.types';

注意:使用import type可显式导入类型(如import type { User } from './types'),进一步强化类型与值的分离,避免类型导入被误用作运行时值。

实战案例分析

1. 前端工具函数模块
在前端项目中,工具函数通常按功能拆分并通过索引模块汇总导出,提升导入便捷性:

typescript
复制代码
// math.ts - 导出具体工具函数
export function add(a: number, b: number): number {
  return a + b;
}

export function sub(a: number, b: number): number {
  return a - b;
}

// index.ts - 汇总导出
export * from './math';
export * from './string';
export * from './date';

// 使用时直接从索引模块导入
import { add, formatDate } from './utils';

2. 后端路由模块
在Node.js后端开发中,路由配置常按资源拆分,通过模块导入实现路由注册的解耦:

typescript
复制代码
// user.routes.ts - 导出路由配置
import { Router } from 'express';
const router = Router();

router.get('/users', (req, res) => res.json([]));
router.post('/users', (req, res) => res.status(201).json({}));

export default router;

// app.ts - 导入并注册路由
import express from 'express';
import userRouter from './routes/user.routes';

const app = express();
app.use('/api', userRouter); // 挂载用户路由

实践练习:用户类模块拆分

基于上一章实现的“用户类”,按以下步骤进行模块拆分:

  1. 创建User.ts模块:定义并导出User类及相关类型
typescript
复制代码
// User.ts
export type UserRole = 'admin' | 'user' | 'guest';

export class User {
  constructor(
    public id: number,
    public name: string,
    public role: UserRole = 'user'
  ) {}

  isAdmin(): boolean {
    return this.role === 'admin';
  }
}
  1. 在主文件中导入使用
typescript
复制代码
// main.ts
import { User, UserRole } from './User';

const admin: User = new User(1, 'Admin', 'admin');
console.log(admin.isAdmin()); // 输出 true

通过模块拆分,代码结构更清晰,User类的复用与维护性显著提升,同时类型与值的显式分离也增强了代码的可读性与安全性。

模块系统作为TypeScript项目的基础架构,其规范的应用直接影响项目的可维护性。合理选择导出方式(命名/默认/类型)、建立清晰的模块层次,是构建可扩展应用的关键前提。后续章节将进一步探讨模块解析策略、路径别名配置等高级应用。