模块系统概述
概述
TypeScript 支持多种模块系统,主要包括 ES 模块(ES Modules) 和 CommonJS 模块。模块系统允许我们将代码组织成独立的、可重用的单元,通过导入(import)和导出(export)机制实现代码的模块化管理。理解模块系统是构建大型 TypeScript 应用的基础。
模块系统类型
ES 模块(ES Modules)
ES 模块是 JavaScript 的官方模块标准,使用 import 和 export 关键字。这是现代 TypeScript 项目推荐使用的模块系统。
// math.ts - 导出模块
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
// 默认导出
export default function multiply(a: number, b: number): number {
return a * b;
}// app.ts - 导入模块
import { add, subtract } from './math';
import multiply from './math'; // 默认导入
const result1 = add(5, 3); // 8
const result2 = subtract(10, 4); // 6
const result3 = multiply(2, 4); // 8CommonJS 模块
CommonJS 是 Node.js 使用的模块系统,使用 require 和 module.exports。TypeScript 也支持 CommonJS 语法。
// utils.ts - CommonJS 导出
function greet(name: string): string {
return `Hello, ${name}!`;
}
module.exports = {
greet
};
// 或者使用 exports
exports.greet = greet;// app.ts - CommonJS 导入
const { greet } = require('./utils');
const message = greet('TypeScript'); // "Hello, TypeScript!"提示
在现代 TypeScript 项目中,推荐使用 ES 模块语法,即使编译目标是 CommonJS,也可以使用 import/export 语法,TypeScript 编译器会自动转换。
模块的基本概念
模块的作用域
每个模块都有自己的作用域,模块内的变量、函数、类等默认是私有的,只有通过 export 导出的内容才能被其他模块访问。
// user.ts
// 私有变量,外部无法访问
let privateCounter = 0;
// 导出的函数,外部可以访问
export function getUserCount(): number {
return privateCounter;
}
export function incrementUserCount(): void {
privateCounter++;
}
// 私有函数,外部无法访问
function validateUser(user: string): boolean {
return user.length > 0;
}// app.ts
import { getUserCount, incrementUserCount } from './user';
incrementUserCount();
console.log(getUserCount()); // 1
// ❌ 错误:无法访问私有变量和函数
// console.log(privateCounter); // 编译错误
// validateUser('test'); // 编译错误模块的编译目标
TypeScript 编译器可以根据 tsconfig.json 中的 module 选项,将 ES 模块语法编译为不同的模块系统:
{
"compilerOptions": {
"module": "ES2020", // 编译为 ES 模块
"module": "CommonJS", // 编译为 CommonJS
"module": "AMD", // 编译为 AMD
"module": "UMD" // 编译为 UMD
}
}使用示例
示例 1:创建和使用工具模块
// utils/string-utils.ts
export function capitalize(str: string): string {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
export const STRING_CONSTANTS = {
EMPTY: '',
SPACE: ' '
} as const;// app.ts
import { capitalize, reverse, STRING_CONSTANTS } from './utils/string-utils';
const name = capitalize('typescript'); // "TypeScript"
const reversed = reverse('hello'); // "olleh"
console.log(STRING_CONSTANTS.SPACE); // " "示例 2:模块的重新导出
可以使用 export ... from 语法重新导出其他模块的内容,这对于创建统一的模块入口非常有用。
// math/add.ts
export function add(a: number, b: number): number {
return a + b;
}
// math/subtract.ts
export function subtract(a: number, b: number): number {
return a - b;
}
// math/index.ts - 统一导出
export { add } from './add';
export { subtract } from './subtract';
// 也可以导出所有内容
// export * from './add';
// export * from './subtract';// app.ts - 从统一入口导入
import { add, subtract } from './math';
const sum = add(1, 2); // 3
const diff = subtract(5, 2); // 3示例 3:类型和值的导出
TypeScript 模块可以同时导出类型和值,导入时可以使用 type 关键字明确表示导入的是类型。
// types.ts
export interface User {
name: string;
age: number;
}
export type Status = 'active' | 'inactive';
export class UserService {
getUser(id: number): User {
return { name: 'John', age: 30 };
}
}// app.ts
// 导入类型和值
import { User, Status, UserService } from './types';
// 或者明确指定类型导入(推荐,有助于 tree-shaking)
import type { User, Status } from './types';
import { UserService } from './types';
const user: User = { name: 'Alice', age: 25 };
const status: Status = 'active';
const service = new UserService();模块解析
TypeScript 使用模块解析策略来确定导入语句引用的文件位置。主要有两种策略:
1. Classic 解析策略
传统的解析策略,主要用于向后兼容。
2. Node 解析策略(推荐)
模拟 Node.js 的模块解析机制,这是现代项目的推荐选择。
{
"compilerOptions": {
"moduleResolution": "node"
}
}模块解析会按照以下顺序查找模块:
- 相对路径:
./module或../module - 绝对路径:
/path/to/module - Node 模块:从
node_modules目录查找 - 路径映射:根据
tsconfig.json中的paths配置
注意事项
注意
模块系统选择:根据项目目标环境选择合适的模块系统。浏览器环境通常使用 ES 模块,Node.js 环境可以使用 CommonJS 或 ES 模块。
循环依赖:避免模块之间的循环依赖,这可能导致运行时错误或未定义的行为。
默认导出:每个模块只能有一个默认导出,但可以有多个命名导出。
类型导入:使用
import type导入类型可以确保这些导入在编译后会被移除,有助于代码优化。
最佳实践
- 优先使用 ES 模块语法(
import/export) - 使用
import type导入类型,提高代码清晰度和性能 - 通过
index.ts文件创建统一的模块入口 - 避免使用
export *导出所有内容,明确导出需要的项
相关链接
- 导入和导出 - 详细了解 import 和 export 的使用
- 模块解析 - 深入了解模块解析策略
- 命名空间 - 了解 TypeScript 命名空间
- TypeScript 官方文档 - 模块