TypeScript 模块系统
模块系统是现代 TypeScript 开发的基础。
TypeScript 完全支持 ES Module 语法,并提供了丰富的模块解析策略。
通过模块系统,可以将代码分割成可重用的单元,实现代码组织和复用。
为什么需要模块系统
随着项目规模增长,代码量会越来越大。
将代码分散到多个文件中,通过模块组织,可以提高代码的可维护性和可复用性。
模块系统让每个文件都有自己的作用域,避免全局变量污染。
概念说明:模块是包含导出和导入语句的 TypeScript 文件。通过 export 导出内容,通过 import 导入内容。
模块导出
使用 export 关键字可以将变量、函数、类、接口等导出供其他模块使用。
user.ts 模块
export var name = "Alice";
export const age = 25;
// 导出函数
export function greet(message: string): string {
return "Hello, " + message;
}
// 导出类
export class User {
// 构造函数参数属性
constructor(public name: string) {}
// 自我介绍方法
introduce(): string {
return "I am " + this.name;
}
}
// 导出接口(接口在编译后会消失,仅用于类型检查)
export interface Config {
// 配置主机
host: string;
// 配置端口
port: number;
}
// 批量导出:重命名导出
export { name as userName, age as userAge };
注意:接口和类型在编译后的 JavaScript 中不会产生实际代码,它们仅用于 TypeScript 的类型检查。
模块导入
使用 import 关键字从其他模块导入导出的内容。
main.ts 导入模块
import { name, age, greet } from "./user";
// 默认导入:导入模块的默认导出
import User from "./user";
// 全部导入:将模块所有导出放入一个对象
import * as UserModule from "./user";
// 重命名导入:避免命名冲突
import { greet as sayHello } from "./user";
// 使用导入的内容
console.log(greet("World"));
console.log(sayHello("TypeScript"));
运行结果:
Hello, World Hello, TypeScript
路径说明:导入路径可以是相对路径(如
./user)或绝对路径(如@/utils)。
默认导出
每个模块可以有一个默认导出。
默认导出在导入时不需要使用花括号,且可以取任意名字。
math.ts 模块
export default function add(a: number, b: number): number {
return a + b;
}
// 可以和其他导出混合使用
export function multiply(a: number, b: number): number {
return a * b;
}
main.ts 导入
import add from "./math";
// 导入命名导出:需要使用花括号
import { multiply } from "./math";
console.log("加法: " + add(2, 3));
console.log("乘法: " + multiply(4, 5));
运行结果:
加法: 5 乘法: 20
建议:对于工具函数、类等主要导出内容使用默认导出,对于辅助函数、接口等使用命名导出。
重新导出
重新导出(Re-export)用于聚合多个模块的内容,或将一个模块的导出暴露给另一个模块。
index.ts 聚合模块
export { name, age } from "./user";
// 重新导出默认导出(需要重命名)
export { default as User } from "./user";
// 重新导出所有内容
export * from "./math";
应用场景:使用 index.ts 作为入口文件,集中导出子模块的内容,方便统一导入。
模块解析策略
TypeScript 提供了多种模块解析策略,用于查找导入的模块。
可以在 tsconfig.json 中配置。
tsconfig.json 配置
"compilerOptions": {
// Node 解析策略
// 遵循 Node.js 的模块解析规则
"moduleResolution": "node",
// 经典解析策略
// TypeScript 早期版本使用的策略
"moduleResolution": "classic",
// base URL:设置基础路径
// 所有非相对路径导入都基于此路径解析
"baseUrl": "./src",
// 路径映射:为导入路径设置别名
"paths": {
// @ 开头的导入映射到 src 目录
"@/*": ["./*"],
// @components 开头的导入映射到 components 目录
"@components/*": ["./components/*"]
}
}
}
配置建议:新项目推荐使用 Node 解析策略,它是目前最常用的方式。
动态导入
动态导入(Dynamic Import)使用 import() 语法,可以在运行时按需加载模块。
这对于代码分割、懒加载非常有用。
实例
// import() 返回一个 Promise
async function loadMath() {
// 动态导入模块,只有执行到这里才会加载
var math = await import("./math");
// math.default 是默认导出的函数
console.log("动态加法: " + math.default(1, 2));
}
// 调用懒加载函数
loadMath();
// 条件导入:根据条件动态加载不同模块
async function loadFeature(enable: boolean) {
if (enable) {
// 只有条件满足时才加载模块
var feature = await import("./feature");
feature.run();
}
}
// 根据条件加载
loadFeature(true);
运行结果:
动态加法: 3
性能优化:动态导入可以实现代码分割,只在需要时加载额外的代码,减少初始加载时间。
注意事项
- 相对路径:使用相对路径导入本地模块(./、../)
- 模块扩展名:TypeScript 编译时会自动处理扩展名
- 默认 vs 命名:每个模块一个默认导出,多个命名导出
- esModuleInterop:启用此选项可以更方便地导入 CommonJS 模块
最佳实践:保持导入路径一致,使用路径别名简化长路径,建立清晰的模块组织结构。
总结
模块系统是 TypeScript 项目组织的核心。
- export:导出变量、函数、类、接口
- import:导入已导出的内容
- 默认导出:每个模块一个,使用灵活
- 重新导出:聚合模块,创建入口文件
- 动态导入:懒加载,优化性能
- 模块解析:配置路径别名和解析策略
建议:合理组织模块结构,使用路径别名简化导入,建立清晰的导出导入规范。
点我分享笔记