什么是 TypeScript 模块?
在 JavaScript 项目中,随着代码量的增长,文件越来越多,逻辑越来越复杂,很容易陷入“全局变量污染”和“函数命名冲突”的困境。想象一下:你和同事同时写了一个叫 getUserInfo() 的函数,结果运行时谁覆盖了谁,调试起来非常头疼。
TypeScript 模块就是为了解决这个问题而生的。它把代码划分成独立的、可复用的单元,每个模块内部的变量和函数默认是私有的,不会自动暴露到全局作用域。这就像把一个大工厂拆分成多个车间,每个车间有自己的工具和工人,互不干扰,但又能通过标准接口协作。
在 TypeScript 中,模块是基于 ES6 的 import 和 export 语法实现的。它们不仅让代码更清晰,还能配合构建工具(如 Webpack、Vite)进行优化,比如代码分割和 tree-shaking(移除未使用的代码)。
💡 提示:TypeScript 模块是现代前端开发的基石,无论是开发库、框架,还是大型应用,几乎都依赖模块系统来组织代码。
模块的基本语法:export 与 import
TypeScript 模块的核心是 export 和 import。你可以把它们理解为“打包”和“拆包”——把一组功能打包成一个模块,再在其他地方导入使用。
命名导出(Named Export)
命名导出允许你导出多个函数、变量或类。每个导出都有一个名字,导入时必须用相同的名字。
// mathUtils.ts
export const PI = 3.14159;
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
在另一个文件中,你可以这样导入:
// app.ts
import { PI, add, multiply } from './mathUtils';
console.log(PI); // 输出:3.14159
console.log(add(2, 3)); // 输出:5
console.log(multiply(4, 5)); // 输出:20
✅ 说明:
export关键字让这些内容可以被外部访问;import从指定路径加载模块,花括号{}中的名字必须与导出的名字一致。
默认导出(Default Export)
默认导出只允许一个,通常用于模块的主要功能。导入时不需要花括号,名字可以自定义。
// logger.ts
const log = (message: string): void => {
console.log(`[LOG] ${message}`);
};
export default log; // 只能有一个默认导出
导入时:
// main.ts
import log from './logger'; // 可以随便命名,比如 import logger from './logger'
log('程序启动成功'); // 输出:[LOG] 程序启动成功
⚠️ 注意:默认导出不能与命名导出共存于同一个文件中,除非你明确区分。但一个文件可以同时有默认导出和命名导出。
模块路径与文件结构
在 TypeScript 中,模块的路径是相对路径或绝对路径。推荐使用相对路径,清晰明了。
相对路径
// app.ts
import { add } from './utils/math'; // 当前目录下的 utils/math.ts
import { config } from '../config'; // 上一级目录的 config.ts
绝对路径(通过 tsconfig.json 配置)
你可以通过 tsconfig.json 设置别名,让导入更简洁。
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
}
}
}
然后就可以这样导入:
import { formatDate } from '@utils/date'; // 等价于 src/utils/date.ts
import { Button } from '@components/Button'; // 等价于 src/components/Button.ts
🧩 小贴士:别名能大幅提升项目结构的可读性,尤其在大型项目中非常实用。
模块的类型定义与类型导出
TypeScript 模块不仅能导出函数和变量,还能导出类型定义,让类型系统在模块之间保持一致。
// types.ts
export type User = {
id: number;
name: string;
email: string;
};
export interface Post {
title: string;
content: string;
author: User;
}
在其他文件中使用:
// blog.ts
import { User, Post } from './types';
const author: User = {
id: 1,
name: '张三',
email: 'zhangsan@example.com'
};
const post: Post = {
title: 'TypeScript 模块入门',
content: '本文介绍模块的基本用法...',
author
};
console.log(post.title); // 输出:TypeScript 模块入门
📌 重点:类型导出是模块化开发中非常关键的一环。它让团队成员之间能共享类型,避免“类型不一致”问题。
模块的命名空间 vs 模块(历史对比)
在早期的 TypeScript 版本中,有“命名空间”(namespace)这一概念,用于组织代码。但现在,模块已经取代了命名空间,成为主流方式。
| 特性 | 命名空间(namespace) | 模块(module) |
|---|---|---|
| 作用域 | 全局命名空间内 | 独立作用域 |
| 导出方式 | export 关键字(在命名空间内) |
export 关键字(在模块顶层) |
| 文件粒度 | 一个文件一个命名空间 | 一个文件一个模块 |
| 推荐使用 | ❌ 已弃用 | ✅ 强烈推荐 |
🔍 说明:虽然命名空间在 TypeScript 中仍可使用,但现代项目应统一使用模块。模块是 ES6 的标准,兼容性好,且支持 tree-shaking,更利于性能优化。
实战案例:构建一个小型工具库
我们来实战一个完整的 TypeScript 模块项目,模拟一个“工具库”模块,包含日期处理、字符串工具和配置管理。
项目结构
utils/
├── date.ts
├── string.ts
├── config.ts
└── index.ts
date.ts:日期处理模块
// date.ts
import { format } from 'date-fns'; // 日期格式化库(需 npm install date-fns)
// 格式化日期为 YYYY-MM-DD
export function formatDate(date: Date): string {
return format(date, 'yyyy-MM-dd');
}
// 获取今天是星期几(中文)
export function getWeekday(date: Date): string {
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
return `星期${weekdays[date.getDay()]}`;
}
string.ts:字符串工具
// string.ts
// 去除首尾空格并转为小写
export function cleanString(str: string): string {
return str.trim().toLowerCase();
}
// 判断是否为邮箱格式
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
config.ts:配置管理
// config.ts
// 应用配置项
export const APP_CONFIG = {
apiUrl: 'https://api.example.com',
timeout: 5000,
debug: false
};
index.ts:统一导出入口
// index.ts
// 为外部提供统一的入口,隐藏内部结构
export { formatDate, getWeekday } from './date';
export { cleanString, isValidEmail } from './string';
export { APP_CONFIG } from './config';
使用模块库
// main.ts
import { formatDate, getWeekday, cleanString, isValidEmail, APP_CONFIG } from './utils';
console.log(formatDate(new Date())); // 输出:2024-04-05
console.log(getWeekday(new Date())); // 输出:星期五
console.log(cleanString(' Hello World ')); // 输出:hello world
console.log(isValidEmail('test@example.com')); // 输出:true
console.log(APP_CONFIG.apiUrl); // 输出:https://api.example.com
✅ 这个案例展示了模块的三大优势:
- 可维护性:功能按职责拆分
- 可复用性:其他项目可直接引用
utils模块- 可读性:通过
index.ts统一入口,调用更简洁
常见问题与最佳实践
1. 模块导入路径错误?
常见错误:
import { add } from './math'; // 错误:文件名是 math.ts,但路径写成 math
✅ 正确做法:
import { add } from './math'; // 注意文件扩展名:.ts 可省略,但建议保留
2. 模块循环依赖?
当 A 模块导入 B,B 又导入 A,就会形成循环依赖。
// a.ts
import { bFunction } from './b'; // 导入 b
export function aFunction() {
console.log('A');
bFunction(); // 调用 b
}
// b.ts
import { aFunction } from './a'; // 导入 a
export function bFunction() {
console.log('B');
aFunction(); // 调用 a
}
❌ 这会导致运行时错误。
✅ 解决方案:重构代码,将共同逻辑提取到第三方模块,或延迟导入。
3. 最佳实践总结
- 每个文件一个模块,职责单一
- 使用
index.ts统一导出,提升可维护性 - 避免循环依赖,尽早拆分公共逻辑
- 使用
type和interface做类型定义,增强类型安全 - 配置
tsconfig.json的moduleResolution为node16或bundler,提升导入体验
结语
TypeScript 模块是构建现代 JavaScript 应用的基石。它不仅解决了代码组织混乱的问题,还为类型安全、代码复用和构建优化提供了强大支持。
无论你是初学者还是中级开发者,掌握模块的使用方式,都是迈向专业开发的第一步。从今天开始,把你的代码拆成模块,让项目更清晰、更健壮。
当你在项目中看到 import 和 export 时,别再觉得它们只是语法糖——它们是你代码结构的骨架,是团队协作的桥梁,更是未来可维护性的保证。