TypeScript 模块系统

模块系统是现代 TypeScript 开发的基础。

TypeScript 完全支持 ES Module 语法,并提供了丰富的模块解析策略。

通过模块系统,可以将代码分割成可重用的单元,实现代码组织和复用。


模块导出导入流程 user.ts export const name = "Alice" export class User { ... } export interface Config { ... import main.ts import { name, User } from "./user"; import User from "./user" use 运行结果 JavaScript 执行代码 导出导入方式 命名导出/导入 默认导出/导入 重新导出 动态导入

为什么需要模块系统

随着项目规模增长,代码量会越来越大。

将代码分散到多个文件中,通过模块组织,可以提高代码的可维护性和可复用性。

模块系统让每个文件都有自己的作用域,避免全局变量污染。

概念说明:模块是包含导出和导入语句的 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:导入已导出的内容
  • 默认导出:每个模块一个,使用灵活
  • 重新导出:聚合模块,创建入口文件
  • 动态导入:懒加载,优化性能
  • 模块解析:配置路径别名和解析策略

建议:合理组织模块结构,使用路径别名简化导入,建立清晰的导出导入规范。