目录
[export = 和 import = require()](#export = 和 import = require())
[ECMAScript 模块 (ESM)](#ECMAScript 模块 (ESM))
[CommonJS 模块](#CommonJS 模块)
[AMD 模块 (异步模块定义)](#AMD 模块 (异步模块定义))
[UMD 模块 (通用模块定义)](#UMD 模块 (通用模块定义))
在 TypeScript 1.5 版本及之后,术语发生了一些变化,特别是关于模块的称谓。
- 内部模块变为命名空间: 在 TypeScript 1.5 之前,称为"内部模块"的概念现在被称为"命名空间"。这个变化是为了与 ECMAScript 2015(ES6)中的术语保持一致。
- 外部模块简称为模块: 同时,"外部模块"现在被简称为"模块"。这种改变旨在消除术语上的混淆,使得 TypeScript 的模块系统更符合 JavaScript 标准。
介绍
模块是 JavaScript 中用来组织和复用代码的重要机制。从 ECMAScript 2015 开始,JavaScript 正式引入了模块的概念,这一概念在 TypeScript 中也得到了支持和扩展。
- 作用域限制: 模块在自身的作用域内执行,不会污染全局作用域。模块内部定义的变量、函数、类等,默认情况下对外部是不可见的,除非使用 export 明确导出。
- 导入和导出: 若要在模块之间共享功能,需要使用 export 将变量、函数、类等导出;同时使用 import 来引入其它模块导出的内容。这种显式的导入和导出机制使得模块之间的依赖关系明确且可控。
- 文件级别模块: 在 TypeScript 中,任何包含顶级 import 或 export 声明的文件被视为一个模块。这意味着 TypeScript 中的模块系统与 ECMAScript 2015 的模块系统保持一致,同时也兼容 CommonJS 等其它模块加载器。
- 模块加载器: 在运行时,模块加载器负责查找和执行模块的依赖关系。Node.js 中常用的是 CommonJS,而浏览器端常使用 Require.js 等工具。
- 自声明性: 模块在 TypeScript 中是自声明的,模块之间的关系通过 import 和 export 在文件级别上建立,而不是通过全局变量或函数的引用。
导出
导出声明
在 TypeScript 或 JavaScript 中,可以通过在声明前加上 export 关键字来导出变量、函数、类、类型别名或接口等内容。
TypeScript
// 导出一个函数
export function greet(name: string) {
return `你好, ${name}!`;
}
导出语句
导出语句允许我们导出多个声明,同时也可以通过重命名来提供更友好的导出名称。这在需要整合多个导出或者提供更具描述性的导出名称时非常有用。
TypeScript
// 导出并重命名一个函数
function sayHello(name: string) {
return `你好, ${name}!`;
}
export { sayHello as greet };
// 现在 'greet' 被导出作为 'sayHello' 的别名
重新导出
重新导出允许我们从其它模块导出它们已经导出的内容,这样一个模块可以扩展另一个模块而无需在当前模块中引入它们。
TypeScript
// 从另一个模块 './greetings' 中重新导出 'sayHello'
export { sayHello } from './greetings';
// 现在 './greetings' 中的 'sayHello' 被重新导出到当前模块
聚合重新导出
在 TypeScript 中,可以使用 export * from "module" 的语法将一个模块中的所有导出聚合到一个单一的导出语句中。这样做可以简化多模块的导出管理。
TypeScript
// 聚合导出 './greetings' 模块中的所有导出
export * from './greetings';
// 现在 './greetings' 中的所有导出都从当前模块重新导出
导入
模块的导入操作与导出一样简单。 可以使用以下 import
形式之一来导入其它模块中的导出内容。
导入一个模块中的某个导出内容
TypeScript
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
可以对导入内容重命名
TypeScript
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
将整个模块导入到一个变量,并通过它来访问模块的导出部分
TypeScript
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
导入具有副作用的模块
有些模块在导入时并不导出任何具体的变量或函数,它们的作用可能是执行一些全局操作或设置一些全局状态,这种模块被称为具有副作用的模块。
在 TypeScript 或 JavaScript 中,可以使用如下形式导入这类模块:
TypeScript
import './styles.css'; // 导入具有副作用的 CSS 文件
import './setup'; // 导入具有副作用的设置文件或模块
这种导入方式告诉 TypeScript 或 JavaScript 运行时去执行被导入模块的全局操作或副作用,而不关心模块本身是否导出了任何内容。
默认导出
默认导出在模块化开发中非常便利,它允许我们为模块指定一个主要的导出内容,而无需显式地命名。
定义默认导出: 使用 export default 关键字可以指定一个模块的默认导出。
TypeScript
// 某个模块导出一个默认的函数
export default function() {
console.log('This is the default export function');
}
导入默认导出: 在导入时,需要使用特定的语法形式来引入默认导出内容。
TypeScript
// 导入默认导出的函数
import defaultFunction from './module';
// 调用默认导出的函数
defaultFunction();
这里的 defaultFunction 是从模块 ./module 中导入的默认导出内容。
适用场景
- 单一主导出: 默认导出适合那些只需导出单一功能或对象的模块。例如,一个库可以将其主要的类或函数作为默认导出,使得使用者可以直接引入并使用这个主要功能。
- 方便性: 对于一些常见的库或工具,如 jQuery 或 lodash,它们可以通过默认导出方式使得使用者可以方便地直接使用它们的核心功能。
注意事项
- 唯一性: 每个模块只能有一个默认导出。如果一个模块已经使用了 export default 导出了某个对象或函数,再次使用 export default 导出另一个内容会导致语法错误。
- 区分命名导出: 默认导出在导入时不需要使用花括号 {},而命名导出则需要。这使得在代码中可以清晰地区分使用默认导出和命名导出。
export = 和 import = require()
export = 和 import = require() 是用于在 TypeScript 中处理旧版 CommonJS 模块的语法。这种语法通常在需要与老旧 JavaScript 模块系统(如 Node.js 中的 CommonJS)进行兼容时使用。
export = 语法允许将整个模块作为单一对象进行导出,以便与 CommonJS 和 AMD 等旧模块系统兼容。
TypeScript
// module.ts
const myVar = 123;
function myFunction() {
console.log('Hello, world!');
}
export = {
myVar,
myFunction
};
import module = require("module") 是一种特定于 TypeScript 的语法,用于导入使用 export = 导出的模块。
TypeScript
// main.ts
import module = require("./module");
console.log(module.myVar); // 输出 123
module.myFunction(); // 输出 "Hello, world!"
区别与注意事项
- 兼容性: export = 和 import = require() 主要用于与 CommonJS 和 AMD 等旧模块系统的兼容性,不适用于 ES6 模块系统。
- 使用场景: 在现代 TypeScript 和 JavaScript 开发中,推荐使用 ES6 的 export 和 import 语法,因为它们更加标准和直观,同时也能兼容 CommonJS 环境,但不支持 AMD 环境。
- 默认导出区别: export default 语法与 export = 不同,后者更加灵活,因为它可以导出任何 TypeScript 支持的模块实体(类、函数、对象等),而 export default 只能导出单个默认值。
总结来说,export = 和 import = require() 是为了在需要与旧版模块系统兼容时使用的特定 TypeScript 语法,能够有效处理 CommonJS 和 AMD 环境下的模块导出和导入需求。
生成模块代码
根据编译时指定的模块目标参数,编译器会生成相应的供Node.js (CommonJS),Require.js (AMD),UMD,SystemJS或ECMAScript 2015 native modules (ES6)模块加载系统使用的代码。 想要了解生成代码中 define
,require
和 register
的意义,请参考相应模块加载器的文档。
ECMAScript 模块 (ESM)
ECMAScript 模块是现代 JavaScript 的标准模块系统,支持通过 import 和 export 来导入和导出模块。
TypeScript
// module.ts
export const myVar = 123;
export function myFunction() {
console.log('Hello, world!');
}
// main.ts
import { myVar, myFunction } from './module';
console.log(myVar); // 输出 123
myFunction(); // 输出 "Hello, world!"
CommonJS 模块
CommonJS 是 Node.js 默认的模块系统,使用 require() 导入模块,使用 module.exports 或 exports 导出模块。
TypeScript
// module.js
const myVar = 123;
function myFunction() {
console.log('Hello, world!');
}
module.exports = {
myVar,
myFunction
};
// main.js
const { myVar, myFunction } = require('./module');
console.log(myVar); // 输出 123
myFunction(); // 输出 "Hello, world!"
AMD 模块 (异步模块定义)
AMD 是一种用于浏览器环境的异步模块定义规范,主要用于在浏览器中异步加载模块。
TypeScript
// module.js
define('module', [], function() {
const myVar = 123;
function myFunction() {
console.log('Hello, world!');
}
return {
myVar,
myFunction
};
});
// main.js
require(['module'], function(module) {
console.log(module.myVar); // 输出 123
module.myFunction(); // 输出 "Hello, world!"
});
UMD 模块 (通用模块定义)
UMD 是一种通用的模块定义,可以兼容多种模块加载系统,包括 CommonJS、AMD 和全局变量导出。
TypeScript
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory();
} else {
// Browser globals
root.myModule = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
const myVar = 123;
function myFunction() {
console.log('Hello, world!');
}
return {
myVar,
myFunction
};
}));
选择合适的模块系统
- ECMAScript 模块 (ESM) 是未来 JavaScript 模块的标准,适合于现代浏览器和支持 ESM 的 Node.js 环境。
- CommonJS 模块 适用于 Node.js 等不支持 ECMAScript 模块的环境,它是 Node.js 默认的模块系统。
- AMD 模块 适合于浏览器环境,特别是需要异步加载模块的情况。
- UMD 模块 则是一种通用的兼容模块定义,可以在各种环境中使用。
创建模块结构指导
1、避免深层嵌套和命名空间:
- 命名空间虽然有其用途,但在模块中使用会增加不必要的层级。模块本身已经通过文件系统有了逻辑组织,不需要额外的命名空间来分组。
- 如果可能,避免在顶层使用命名空间导出内容。将内容直接导出到模块的顶层,让用户可以直接访问。
2、使用 export default:
- 当模块只导出单个对象(类或函数)时,考虑使用 export default。这样可以简化导入语法,用户不需要花费额外的心智去理解导出的名称。
3、直接导出多个对象:
- 如果模块需要导出多个对象,确保将它们放在顶层直接导出,而不是通过命名空间。这样用户在导入时可以明确列出需要的内容,而无需处理额外的层级。
4、避免导出单一类或函数:
- 如果模块只导出一个类或函数,并且没有其他辅助内容需要导出,考虑使用 export default。这种方式对用户来说更加简洁和直观。
5、使用重新导出进行扩展:
- 如果需要扩展其他模块的功能,可以使用重新导出(export * from 'other-module')来将其他模块的内容重新导出到当前模块的顶层,而不是在命名空间中导出。
6、组织文件结构:
- 使用文件系统的目录结构来组织模块文件,这样可以更清晰地反映模块的功能和关系。避免一个目录下有过多文件,可以考虑进一步细分子目录。
更多关于模块和命名空间的资料查看命名空间和模块