返回 导航

其他

hangge.com

TypeScript - 模块化开发与命名空间使用详解(ESModule、CommonJS、namespace)

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

一、模块化开发

1,什么是模块

(1)在 TypeScript 中,任何包含顶级 importexport 的文件都被视为一个模块。相反,不包含任何顶级 importexport 的文件被视为脚本,其内容在全局作用域中可用。
提示ES ModulesJavaScript 官方的模块标准,于 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)通过 baseUrlpaths 配置路径别名,简化导入路径:
// 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 中可以混用 ESMCommonJS 模块。需要在 tsconfig.json 中配置:
// tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,      // 启用 CommonJS 模块兼容
    "allowSyntheticDefaultImports": true  // 允许默认导入 CommonJS 模块
  }
}

(2)导入 CommonJS 模块:
注意ESMCJS 混用时,不同运行时环境支持程度不同。浏览器不支持 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 标准语法,具有更好的生态兼容性
  • 现代打包工具(webpackvite)对模块有更好的支持
  • 代码分割、懒加载等特性基于模块实现

(2)命名空间适用于以下场景:
  • 在不支持模块系统的旧环境中运行代码
  • 为第三方库编写类型声明文件
  • 需要在全局作用域添加类型定义
提示:由于模块可以取代命名空间,而且是 JavaScript 的标准语法,建议总是优先使用模块,替代命名空间。
评论

全部评论(0)

回到顶部