Skip to content

TypeScript - 模块

模块(Modules)是现代 JavaScript 和 TypeScript 中用于将代码组织成可重用、独立单元的基础概念。它们通过封装代码来帮助管理依赖、防止全局命名空间污染并提高可维护性。

TypeScript 完全支持标准的 ECMAScript (ES) 模块系统,该系统使用 import 和 export 关键字。对于浏览器和 Node.js 环境,这是构建 TypeScript 应用程序推荐的方法。

任何包含顶级 import 或 export 语句的文件都被视为一个模块。在模块中声明的变量、函数、类等,除非明确导出,否则都是该模块的本地成员。

你可以通过添加 export 关键字来导出声明。

// --- mathUtils.ts ---
export const PI = 3.14159;
export function add(x: number, y: number): number {
return x + y;
}
export interface Point {
x: number;
y: number;
}
// 默认导出(每个模块只能有一个)
export default class Calculator {
add(x: number, y: number): number {
return x + y;
}
// ... 其他方法
}

你也可以单独导出成员:

// --- stringUtils.ts ---
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function reverse(str: string): string {
return str.split('').reverse().join('');
}
export { capitalize, reverse }; // 导出命名成员
export { capitalize as cap }; // 以不同名称导出

使用 import 关键字从其他模块访问导出的成员。指定的路径通常是相对路径,或者可以由模块加载器/打包器(如 node_modules)解析。

// --- main.ts ---
// 导入命名导出
import { add, PI, Point } from './mathUtils';
// 导入默认导出(可以使用任何名称)
import MyCalculator from './mathUtils';
// 导入命名导出并使用别名
import { capitalize as makeFirstLetterUpper, reverse } from './stringUtils';
// 将整个模块导入为一个对象
import * as math from './mathUtils';
console.log(`Adding 5 and 3: ${add(5, 3)}`); // 输出: 8
console.log(`PI is approx: ${PI}`); // 输出: 3.14159
const origin: Point = { x: 0, y: 0 };
console.log('Origin:', origin);
const calc = new MyCalculator();
console.log(`Calculator adds 10 and 20: ${calc.add(10, 20)}`); // 输出: 30
console.log(makeFirstLetterUpper('hello')); // 输出: Hello
console.log(reverse('world')); // 输出: dlrow
console.log(`Accessing via module object: ${math.add(1, 1)}`); // 输出: 2
console.log(`Default export via module object: ${new math.default().add(2,2)}`); // 输出: 4

模块解析与执行 (Module Resolution and Execution)

Section titled “模块解析与执行 (Module Resolution and Execution)”

模块如何被找到(解析)和执行取决于目标环境:

  • 浏览器:现代浏览器直接支持 ES 模块,使用 <script type="module" src="...">。然而,为了兼容性、依赖管理和优化,开发者通常使用打包器 (bundlers),如 Webpack、Rollup、Parcel 或 Vite。打包器处理 import/export 语句,解析依赖,并为浏览器创建优化的 JavaScript 文件。
  • **Node.js:**Node.js 内置支持 ES 模块(使用 .mjs 文件或在 package.json 中设置 "type": "module")和旧的 CommonJS 模块系统(require/module.exports)。TypeScript 可以使用 tsconfig.json 中的 module 选项(例如,"module": "NodeNext" 或 "module": "CommonJS")编译成这两种格式中的任何一种。

与模块相关的 tsconfig.json 中的关键选项包括:

  • "module":指定编译后 JavaScript 输出的模块系统(例如,"ES2020"、"CommonJS"、"NodeNext")。
  • "target":指定输出代码的 ECMAScript 版本(影响可用特性)。
  • "moduleResolution":编译器如何查找模块(通常是 "Node" 或 "NodeNext")。
  • "outDir":编译后的 JavaScript 文件存放的目录。
  • "rootDir":指定源文件的根目录(有助于在 outDir 中保持结构)。

命名空间 (Namespaces)(曾称内部模块)

Section titled “命名空间 (Namespaces)(曾称内部模块)”

TypeScript 还有一个概念叫做 namespace。在 ES 模块成为标准之前,命名空间是 TypeScript 中用于将相关代码分组并避免污染全局作用域的主要方式。它们被编译成嵌套的 JavaScript 对象或立即执行函数表达式 (IIFEs)。

虽然 ES 模块现在是组织代码和管理依赖的标准,但命名空间仍然可能对以下情况有用:

  • 在一个大型项目中组织内部代码,其中模块可能感觉过于细粒度。
  • 为暴露全局对象的库构建声明文件 (.d.ts)。
  • 处理旧代码库或依赖于 --outFile 编译的特定构建系统(尽管这现在不太常见了)。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// 使用示例:
let validator: Validation.StringValidator = new Validation.LettersOnlyValidator();
console.log(validator.isAcceptable("HelloWorld")); // true
console.log(validator.isAcceptable("12345")); // false

如果命名空间跨越多个文件,你可能需要 /// <reference path="..." /> 指令或使用 --outFile 编译器选项,但再说一次,ES 模块更自然地处理多文件组织。

对于现代 TypeScript 开发,**强烈推荐使用 ES 模块(import/export)**来组织代码和管理依赖,而不是命名空间。理解命名空间主要是为了与旧代码的兼容性或像全局声明合并 (global declaration merging) 这样的特定用例。