前言
在 TypeScript 中,namespace
和模块(module
)是两种不同的代码组织方式,主要区别如下:
1. 作用域与可见性
-
namespace
通过namespace
声明的类型/值默认是全局可见的(除非在模块文件中使用),适合在非模块化环境中组织代码。例如 React 的类型声明中:declare namespace React { type ElementType = ... // 全局可访问 }
使用时直接通过
React.ElementType
访问。 -
模块
通过import/export
管理的模块具有文件作用域,需要显式导入才能使用:// 模块文件 export type Props = { ... }; // 使用处 import { Props } from './module';
2. 编译行为
-
namespace
编译后会生成闭包代码,通过对象嵌套实现命名空间效果:// 编译结果 var React = {}; React.ElementType = ...;
-
模块
编译为符合 ESM/CommonJS 的模块系统代码,依赖模块加载器(如 Webpack/Rollup)处理依赖关系。
3. 使用场景
-
namespace 适用场景
- 为第三方库(如 React)提供全局类型扩展
- 在非模块化环境中避免命名冲突
- 兼容旧代码(如合并
.d.ts
声明文件)
-
模块适用场景
- 现代前端项目(React/Vue 等)
- 需要树摇(Tree-shaking)优化时
- 明确的依赖管理
4. 在 React 类型声明中的实际应用
在您提到的 React 类型文件(index.d.ts
)中:
export = React; // 导出为模块
export as namespace React; // 同时作为全局命名空间
declare namespace React { // 内部类型通过命名空间组织
type ElementType = ...;
}
这种写法实现了双重导出:
- 模块化环境通过
import React from 'react'
使用 - 非模块化环境通过全局
React.ElementType
直接访问
总结对比表
特性 | namespace | 模块 |
---|---|---|
作用域 | 全局(除非在模块文件中) | 文件作用域 |
依赖管理 | 无自动依赖解析 | 显式 import/export |
编译结果 | 闭包对象嵌套 | ESM/CommonJS 模块代码 |
适用场景 | 全局类型扩展、旧代码兼容 | 现代模块化项目 |
典型用例 | 库的类型声明文件(.d.ts) | 业务代码组件/工具函数 |
在 TypeScript 现代项目中,推荐优先使用模块,namespace 主要用于类型声明文件或兼容旧系统。