TypeScript 模块与命名空间

这一篇聊聊 TypeScript 的模块系统:ES 模块、命名空间、声明合并,
以及模块解析策略。

 1// 命名导出
 2export const name = 'TypeScript';
 3export function greet() {
 4  return 'Hello';
 5}
 6export class User {}
 7
 8// 导出声明
 9const age = 30;
10export { age };
11
12// 重命名导出
13export { age as userAge };
14
15// 默认导出
16export default class MyClass {}
17
18// 导出所有
19export * from './other-module';
20export { specific } from './other-module';
 1// 命名导入
 2import { name, greet, User } from './module';
 3
 4// 重命名导入
 5import { name as moduleName } from './module';
 6
 7// 默认导入
 8import MyClass from './module';
 9
10// 命名空间导入
11import * as Module from './module';
12
13// 混合导入
14import MyClass, { name, greet } from './module';
15
16// 仅导入类型
17import type { User } from './module';
18import { type User, type Admin } from './module';
19
20// 导入并重新导出
21export { name } from './module';
 1// 动态导入返回 Promise
 2async function loadModule() {
 3  const module = await import('./module');
 4  module.greet();
 5}
 6
 7// 条件导入
 8if (condition) {
 9  const module = await import('./module');
10}
 1namespace Validation {
 2  export interface StringValidator {
 3    isAcceptable(s: string): boolean;
 4  }
 5
 6  const lettersRegexp = /^[A-Za-z]+$/;
 7  const numberRegexp = /^[0-9]+$/;
 8
 9  export class LettersOnlyValidator implements StringValidator {
10    isAcceptable(s: string) {
11      return lettersRegexp.test(s);
12    }
13  }
14
15  export class ZipCodeValidator implements StringValidator {
16    isAcceptable(s: string) {
17      return s.length === 5 && numberRegexp.test(s);
18    }
19  }
20}
21
22// 使用
23let validators: { [s: string]: Validation.StringValidator } = {};
24validators['ZIP code'] = new Validation.ZipCodeValidator();
25validators['Letters only'] = new Validation.LettersOnlyValidator();
 1// Validation.ts
 2namespace Validation {
 3  export interface StringValidator {
 4    isAcceptable(s: string): boolean;
 5  }
 6}
 7
 8// LettersOnlyValidator.ts
 9/// <reference path="Validation.ts" />
10namespace Validation {
11  const lettersRegexp = /^[A-Za-z]+$/;
12  export class LettersOnlyValidator implements StringValidator {
13    isAcceptable(s: string) {
14      return lettersRegexp.test(s);
15    }
16  }
17}
18
19// ZipCodeValidator.ts
20/// <reference path="Validation.ts" />
21namespace Validation {
22  const numberRegexp = /^[0-9]+$/;
23  export class ZipCodeValidator implements StringValidator {
24    isAcceptable(s: string) {
25      return s.length === 5 && numberRegexp.test(s);
26    }
27  }
28}
1namespace Shapes {
2  export namespace Polygons {
3    export class Triangle {}
4    export class Square {}
5  }
6}
7
8import polygons = Shapes.Polygons;
9let sq = new polygons.Square();
 1interface Box {
 2  height: number;
 3  width: number;
 4}
 5
 6interface Box {
 7  scale: number;
 8}
 9
10// 两个声明会合并
11let box: Box = {
12  height: 5,
13  width: 6,
14  scale: 10
15};
 1namespace Animals {
 2  export class Zebra {}
 3}
 4
 5namespace Animals {
 6  export interface Legged {
 7    numberOfLegs: number;
 8  }
 9  export class Dog {}
10}
11
12// 合并后包含 Zebra、Legged、Dog
1class Album {
2  label: Album.AlbumLabel;
3}
4
5namespace Album {
6  export class AlbumLabel {}
7}
8
9// Album 类有 label 属性,类型是 Album.AlbumLabel
 1function buildLabel(name: string): string {
 2  return buildLabel.prefix + name + buildLabel.suffix;
 3}
 4
 5namespace buildLabel {
 6  export let suffix = '';
 7  export let prefix = 'Hello, ';
 8}
 9
10// buildLabel 函数有 prefix 和 suffix 属性
 1enum Color {
 2  red = 1,
 3  green = 2,
 4  blue = 4
 5}
 6
 7namespace Color {
 8  export function mixColor(colorName: string) {
 9    if (colorName == 'red') {
10      return Color.red;
11    } else if (colorName == 'green') {
12      return Color.green;
13    } else if (colorName == 'blue') {
14      return Color.blue;
15    }
16  }
17}
18
19// Color 枚举有 mixColor 方法

TypeScript 支持两种模块解析策略:

  • classic:TypeScript 的默认策略(已废弃)
  • node:Node.js 的模块解析策略(推荐)
1{
2  "compilerOptions": {
3    "moduleResolution": "node"
4  }
5}

Node.js 模块解析规则:

  1. 相对路径:./module../module
  2. 绝对路径:/module
  3. 非相对路径:从 node_modules 查找
1// 相对路径
2import { something } from './module';
3import { something } from '../module';
4
5// 非相对路径
6import { something } from 'module';
7import { something } from 'module/submodule';

使用 paths 配置路径别名:

1{
2  "compilerOptions": {
3    "baseUrl": ".",
4    "paths": {
5      "@/*": ["src/*"],
6      "utils/*": ["src/utils/*"]
7    }
8  }
9}
1// 使用路径别名
2import { something } from '@/utils/helper';
3import { something } from 'utils/helper';

TypeScript 会按以下顺序查找类型声明:

  1. package.json 中的 types 字段
  2. @types/*
  3. 同名的 .d.ts 文件
1{
2  "types": "./dist/index.d.ts"
3}

引用另一个文件:

1/// <reference path="Validation.ts" />
2namespace Validation {
3  // 可以使用 Validation.ts 中的类型
4}

引用类型声明包:

1/// <reference types="node" />

引用内置库:

1/// <reference lib="es2015" />
  • 需要 ES 模块支持
  • 需要 tree-shaking
  • 需要动态导入
  • 现代项目推荐使用
1// 模块
2export class User {}
3export function greet() {}
  • 需要声明合并
  • 需要组织相关代码
  • 需要向后兼容
  • 不推荐在新项目中使用
1// 命名空间
2namespace MyApp {
3  export class User {}
4  export function greet() {}
5}
1// types/global.d.ts
2declare global {
3  interface Window {
4    myCustomProperty: string;
5  }
6}
7
8export {};
1// 扩展第三方模块
2declare module 'module-name' {
3  export interface ExtendedType {
4    newProperty: string;
5  }
6}
1// 声明全局变量
2declare const API_URL: string;
3declare function myGlobalFunction(): void;

TypeScript 的模块和命名空间系统:

  • ES 模块:现代标准,支持导入导出、动态导入
  • 命名空间:组织代码的方式,支持声明合并
  • 声明合并:接口、命名空间、类、函数、枚举都可以合并
  • 模块解析:Node.js 风格的模块解析,支持路径映射
  • 三斜线指令:引用其他文件和类型声明

在实际开发中:

  • 优先使用 ES 模块,而不是命名空间
  • 使用声明合并扩展第三方类型
  • 配置路径别名简化导入路径
  • 理解模块解析规则,避免路径问题