TypeScript - 模块化开发与命名空间使用详解(ESModule、CommonJS、namespace)
作者:hangge | 2026-08-14 08:38
在大型项目开发中,代码的组织和管理是非常重要的。TypeScript 支持两种组织代码的方式:模块化开发和命名空间。模块化开发是现代 JavaScript 的标准方式,通过 ESModule 或 CommonJS 规范实现代码的拆分和复用。命名空间则是 TypeScript 早期引入的概念,用于在一个文件内部进行作用域划分。本文将详细介绍这两种方式的使用方法和最佳实践。




一、模块化开发
1,什么是模块
(1)在 TypeScript 中,任何包含顶级 import 或 export 的文件都被视为一个模块。相反,不包含任何顶级 import 或 export 的文件被视为脚本,其内容在全局作用域中可用。
提示:ES Modules 是 JavaScript 官方的模块标准,于 2015 年添加到 ECMAScript 规范中,到 2020 年已在大多数 Web 浏览器和 JavaScript 运行时中得到广泛支持。
(2)模块的特点:
- 独立作用域:模块内的变量、函数、类等不会污染全局作用域
- 显式导出:只有通过 export 导出的内容才能被其他模块访问
- 显式导入:要使用其他模块的内容,必须通过 import 导入
2,导出与导入
(1)export 用于导出变量、函数、类、接口等:
// 导出函数
export function add(num1: number, num2: number) {
return num1 + num2
}
export function sub(num1: number, num2: number) {
return num1 - num2
}
// 导出常量
export const PI = 3.14159
// 导出接口
export interface IPoint {
x: number
y: number
}
// 导出类型别名
export type ID = string | number
(2)import 用于导入其他模块导出的内容:
// 导入指定的导出
import { add, sub } from "./utils/math"
console.log(add(20, 30)) // 50
console.log(sub(20, 30)) // -10

(3)可以使用 as 关键字为导入指定别名:
// 使用 as 重命名导入
import { add as addition, sub as subtraction } from "./utils/math"
console.log(addition(20, 30))

(4)导入整个模块到一个变量:
提示:使用 import * as 可以将模块的所有导出内容导入到一个命名空间对象中,方便统一管理。
// 导入整个模块 import * as math from "./utils/math" console.log(math.add(20, 30)) console.log(math.PI)

3,默认导出
(1)每个模块可以有一个 默认导出(default export),使用 export default 语法:
export default class User {
constructor(public name: string) {}
}
// 或者
const User = class {
constructor(public name: string) {}
}
export default User
(2)默认导出的导入不需要使用花括号:
注意:默认导出在每个模块中只能有一个,通常用于导出模块的主要功能。
// 导入默认导出
import User from "./user"
const user = new User("hangge")
// 默认导出和命名导出可以同时导入
import User, { add, sub } from "./module"
4,重新导出
可以重新导出其他模块的内容,用于创建模块的聚合入口:
提示:使用 index.ts 作为入口文件,集中导出子模块的内容,是组织模块的常用模式。
// 重新导出指定内容
export { add, sub } from "./math"
// 重新导出并重命名
export { add as addition } from "./math"
// 重新导出默认导出
export { default as User } from "./user"
// 重新导出所有内容
export * from "./utils"
// 重新导出为默认导出
export { default } from "./config"
5,路径别名配置
(1)通过 baseUrl 和 paths 配置路径别名,简化导入路径:
// tsconfig.json
{
"compilerOptions": {
// 基准目录
"baseUrl": ".",
// 路径别名
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
(2)使用路径别名简化导入:
提示:路径别名需要在 tsconfig.json 和打包工具(如 vite.config.js)中同时配置,以确保编译时和运行时都能正确解析。
// 不使用别名
import { add } from "../../../utils/math"
// 使用别名
import { add } from "@utils/math"
import Button from "@components/Button"
6,ESModule 与 CommonJS 兼容
(1)在 TypeScript 中可以混用 ESM 和 CommonJS 模块。需要在 tsconfig.json 中配置:
// tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true, // 启用 CommonJS 模块兼容
"allowSyntheticDefaultImports": true // 允许默认导入 CommonJS 模块
}
}
(2)导入 CommonJS 模块:
注意:ESM 与 CJS 混用时,不同运行时环境支持程度不同。浏览器不支持 CJS;打包工具一般同时支持;Node.js 需要特殊处理。
// CommonJS 模块导出
// module.exports = { test: () => console.log('test') }
// 在 TypeScript 中导入
import * as m1 from './m1.cjs'
m1.test()
三、命名空间
1,什么是命名空间
(1)命名空间(namespace) 是 TypeScript 早期引入的概念,主要用于在一个模块内部进行作用域划分,防止命名冲突。
注意:在现代开发中,推荐使用模块化开发代替命名空间,因为模块是 JavaScript 的标准语法,且不需要编译转换。
(2)命名空间的特点:
- 封装代码:将相关的代码组织在一起
- 避免冲突:不同命名空间中的同名标识符不会冲突
- 类型导出:命名空间可以导出类型信息
2,定义命名空间
(1)使用 namespace 关键字定义命名空间:
提示:命名空间内的成员需要使用 export 关键字导出,否则无法在外部访问。
// 声明 Time 命名空间
export namespace Time {
// 在命名空间中定义属性和方法
export function format(time: string[]) {
return time.join('-')
}
export let name: string = "coder"
}
// 声明 Price 命名空间
export namespace Price {
export function format(price: number) {
return price.toFixed(2)
}
}
(2)导入并使用命名空间:
// 导入命名空间
import { Time, Price } from './utils/format'
// 调用命名空间定义的属性和方法
console.log(Time.name) // coder
console.log(Time.format(['2028', '07', '10'])) // 2028-07-10
console.log(Price.format(2999.7834)) // 2999.78

3,嵌套命名空间
命名空间支持嵌套,可以在一个命名空间内部定义另一个命名空间:
提示:使用嵌套命名空间时,必须从最外层开始引用,如 Utils.Message.log()。
namespace Utils {
export namespace Message {
export function log(msg: string) {
console.log(msg)
}
}
export namespace Config {
export const apiUrl = "https://api.example.com"
}
}
// 使用嵌套的命名空间
Utils.Message.log("Hello") // Hello
console.log(Utils.Config.apiUrl) // https://api.example.com
附:模块与命名空间的对比
1,主要区别
(1)模块和命名空间都是用于组织代码的方式,但有以下主要区别:
- 模块:一个文件一个模块,是 JavaScript 标准语法
- 命名空间:一个文件可以有多个命名空间,是 TypeScript 特有语法
- 编译结果:模块不转换语法,命名空间需要编译转换
- 运行环境:模块需要支持模块系统的环境,命名空间可在任何环境运行
(2)模块与命名空间的代码对比:
// 模块方式:每个文件是一个模块
// math.ts
export function add(a: number, b: number) { return a + b }
// 命名空间方式:在一个文件内划分作用域
namespace Math {
export function add(a: number, b: number) { return a + b }
}
2,选择建议
(1)在大多数情况下,推荐使用模块化开发:
- 模块是 JavaScript 标准语法,具有更好的生态兼容性
- 现代打包工具(webpack、vite)对模块有更好的支持
- 代码分割、懒加载等特性基于模块实现
(2)命名空间适用于以下场景:
- 在不支持模块系统的旧环境中运行代码
- 为第三方库编写类型声明文件
- 需要在全局作用域添加类型定义
提示:由于模块可以取代命名空间,而且是 JavaScript 的标准语法,建议总是优先使用模块,替代命名空间。
全部评论(0)