前置知识: TypeScript

命名空间与模块

3 minIntermediate2026/6/13

TypeScript命名空间、ES模块、CommonJS模块、模块解析策略与声明文件详解。

1. TypeScript 模块系统概述

1.1 命名空间 vs 模块

特性命名空间(Namespace)模块(Module)
引入方式全局可用需要import
文件级别可跨文件合并每个文件一个模块
推荐程度旧代码/全局库推荐
依赖管理无(script标签顺序)打包工具处理
作用域全局作用域文件作用域

推荐:新项目使用ES模块,命名空间仅用于兼容旧代码或声明文件。

2. 命名空间

2.1 基本用法

// utils.ts
namespace Utils {
  // 默认私有,export的才可外部访问
  export function formatDate(date: Date): string {
    return date.toISOString().split('T')[0];
  }

  export function clamp(value: number, min: number, max: number): number {
    return Math.min(Math.max(value, min), max);
  }

  // 未export,命名空间外部不可访问
  function internalHelper(): void {
    console.log('Internal');
  }

  // 嵌套命名空间
  export namespace Math {
    export function randomRange(min: number, max: number): number {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
  }
}

// 使用
const today = Utils.formatDate(new Date());
const clamped = Utils.clamp(150, 0, 100);
const random = Utils.Math.randomRange(1, 10);

2.2 跨文件合并

// shapes.ts
namespace Shapes {
  export class Circle {
    constructor(public radius: number) {}
    area(): number {
      return Math.PI * this.radius ** 2;
    }
  }
}

// more-shapes.ts
// 同名命名空间会自动合并
namespace Shapes {
  export class Rectangle {
    constructor(
      public width: number,
      public height: number
    ) {}
    area(): number {
      return this.width * this.height;
    }
  }
}

// 使用时两个类都可用
const circle = new Shapes.Circle(5);
const rect = new Shapes.Rectangle(10, 20);

2.3 命名空间的合并

// 类 + 命名空间合并(类似静态方法)
class Album {
  constructor(public name: string) {}
}

namespace Album {
  // 类的静态工厂方法
  export function fromJSON(json: string): Album {
    const data = JSON.parse(json);
    return new Album(data.name);
  }

  export const DEFAULT = new Album('Untitled');
}

const album = Album.fromJSON('{"name":"Greatest Hits"}');
const defaultAlbum = Album.DEFAULT;

3. ES 模块

3.1 导出

// === 具名导出 ===

// types.ts - 类型导出
export interface User {
  id: number;
  name: string;
  email: string;
}

export type UserRole = 'admin' | 'editor' | 'viewer';

export enum Status {
  Active = 'active',
  Inactive = 'inactive',
}

// utils.ts - 函数导出
export function formatDate(date: Date): string {
  return date.toISOString().split('T')[0];
}

export function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout>;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// === 默认导出 ===
// 每个模块只能有一个默认导出
export default class UserService {
  private users: Map<number, User> = new Map();

  addUser(user: User): void {
    this.users.set(user.id, user);
  }

  getUser(id: number): User | undefined {
    return this.users.get(id);
  }
}

// === 重新导出 ===
export { User, UserRole } from './types';
export { formatDate } from './utils';
export * from './constants'; // 重新导出所有

3.2 导入

// === 具名导入 ===
import { User, UserRole } from './types';
import { formatDate } from './utils';

// === 默认导入 ===
import UserService from './services/UserService';

// === 混合导入 ===
import UserService, { User } from './services/UserService';

// === 命名别名 ===
import { formatDate as fmtDate } from './utils';

// === 全部导入 ===
import * as Utils from './utils';
Utils.formatDate(new Date());

// === 类型导入(仅导入类型,运行时擦除) ===
import type { User, UserRole } from './types';

// TypeScript 3.8+ 内联type修饰符
import { type User, formatDate } from './types';

3.3 动态导入

// 动态导入:按需加载,返回Promise
async function loadChart(): Promise<void> {
  const { Chart } = await import('./chart');
  const chart = new Chart('#container');
  chart.render();
}

// 条件加载
if (featureFlags.advancedEditor) {
  const { AdvancedEditor } = await import('./AdvancedEditor');
  new AdvancedEditor();
}

4. 模块解析

4.1 解析策略

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "bundler" // 推荐
    // "node" | "classic" | "node16" | "nodenext" | "bundler"
  }
}
策略适用场景说明
bundlerVite/Webpack等最灵活,支持导出映射
node16/nodenextNode.js ESM严格的ESM解析
nodeNode.js CJS传统Node解析

4.2 路径映射

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}
// 使用路径别名
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';
import type { User } from '@/types';

4.3 导出映射(package.json exports)

// package.json
{
  "name": "my-lib",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./utils": {
      "types": "./dist/utils.d.ts",
      "import": "./dist/utils.mjs"
    }
  }
}

5. 声明文件

5.1 编写声明文件

// env.d.ts - 全局类型声明
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    API_URL: string;
    DATABASE_URL: string;
  }
}

// 为没有类型的库编写声明
declare module 'untyped-lib' {
  export function doSomething(value: string): number;
  export const version: string;

  interface Options {
    timeout?: number;
    retries?: number;
  }

  export function create(options?: Options): void;
}

// 扩展已有模块
declare module 'express' {
  interface Request {
    userId?: string;
    sessionId?: string;
  }
}

// 全局变量声明
declare const __APP_VERSION__: string;
declare const __DEV__: boolean;

5.2 三斜线指令

/// <reference path="types.d.ts" />
/// <reference types="node" />
/// <reference lib="es2020" />

// 三斜线指令用于:
// - 引入其他声明文件
// - 声明对@types包的依赖
// - 指定需要的lib

6. 常见问题与解决方案

6.1 模块与脚本的区分

// TypeScript根据文件内容判断是模块还是脚本
// 包含 import 或 export → 模块(文件作用域)
// 不包含 → 脚本(全局作用域)

// 空文件想要作为模块:添加空导出
export {};

6.2 循环依赖

// 问题:A导入B,B导入A
// a.ts
import { b } from './b';
export const a = 'a' + b; // b可能是undefined

// 解决方案1:重构,提取共享部分到第三个文件
// 解决方案2:使用延迟导入
export function getB() {
  const { b } = require('./b'); // 运行时导入
  return b;
}

// 解决方案3:使用接口解耦

6.3 CJS与ESM互操作

// tsconfig.json
{
    "compilerOptions": {
        "esModuleInterop": true,     // 允许CJS默认导入
        "allowSyntheticDefaultImports": true,
    }
}

// CJS模块默认导入
import express from 'express';  // 需要esModuleInterop
import * as express from 'express';  // 不需要

// 在ESM中导入CJS
import { readFileSync } from 'fs';  // CJS具名导出

7. 总结与最佳实践

7.1 模块组织原则

  1. 使用ES模块import/export,避免命名空间
  2. 类型导入用 import type:明确区分型和值
  3. 桶文件(index.ts):统一导出入口,但避免过度嵌套
  4. 路径别名:使用 @/ 前缀简化导入路径

7.2 最佳实践

  1. 新项目不用命名空间:ES模块是标准
  2. 声明文件集中管理:放在 src/env.d.tstypes/ 目录
  3. 避免循环依赖:重构代码结构
  4. 使用 exports 字段:库项目配置导出映射
  5. esModuleInterop: true:简化CJS互操作