07 - 模块与声明文件

3 阅读4分钟

模块系统让你组织代码,声明文件让你在 TypeScript 中使用 JavaScript 库。


7.1 ES 模块(ESM)

TypeScript 完全支持 ES Module 语法:

导出

// utils.ts

// 命名导出
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14159;

export interface User {
  name: string;
  age: number;
}

// 默认导出(每个模块只能有一个)
export default class Calculator {
  add(a: number, b: number) { return a + b; }
}

导入

// main.ts

// 导入命名导出
import { add, PI, User } from "./utils";

// 导入默认导出
import Calculator from "./utils";

// 导入并重命名
import { add as sum } from "./utils";

// 导入所有
import * as Utils from "./utils";
Utils.add(1, 2);
Utils.PI;

// 只导入类型(编译后会被移除,不产生运行时代码)
import type { User } from "./utils";

// 混合导入
import Calculator, { add, type User } from "./utils";

重新导出

// index.ts —— 统一导出(桶文件 / barrel file)

export { add, PI } from "./math";
export { formatDate } from "./date";
export type { User, Config } from "./types";

// 重新导出所有
export * from "./math";

// 重新导出并重命名
export { add as sum } from "./math";

7.2 类型导入导出

TypeScript 提供了 type 关键字来标记纯类型导入/导出,确保编译后完全移除:

// types.ts
export interface User {
  name: string;
  age: number;
}

export type Status = "active" | "inactive";

// main.ts

// 方式一:import type(推荐)
import type { User, Status } from "./types";

// 方式二:内联 type
import { type User, type Status } from "./types";

// 方式三:混合值和类型
import { someFunction, type User } from "./module";

💡 使用 import type 的好处:明确表明这是类型导入,编译后不会残留无用代码。


7.3 CommonJS 互操作

在 Node.js 项目中,可能需要与 CommonJS 模块交互:

// CommonJS 风格
const fs = require("fs"); // TS 中不推荐

// 推荐的 TS 写法
import fs from "fs";         // 需要 esModuleInterop: true
import * as fs from "fs";    // 不需要 esModuleInterop

// 导出 CommonJS 格式
export = function add(a: number, b: number) {
  return a + b;
};

// 导入 CommonJS 格式
import add = require("./add");

7.4 声明文件(.d.ts)

声明文件只包含类型信息,不包含实现。它让 TypeScript 理解 JavaScript 代码的类型。

为什么需要声明文件?

// 假设你使用了一个纯 JS 库 my-lib.js
// TypeScript 不知道它的类型,会报错:
import { doSomething } from "my-lib"; // ❌ 找不到类型声明

// 解决方案:创建声明文件 my-lib.d.ts

编写声明文件

// my-lib.d.ts

// 声明模块
declare module "my-lib" {
  export function doSomething(input: string): number;
  export function doOther(items: string[]): boolean;

  export interface Config {
    debug: boolean;
    timeout: number;
  }

  export default class MyLib {
    constructor(config: Config);
    run(): void;
  }
}

全局声明

// global.d.ts

// 声明全局变量(比如通过 CDN 引入的库)
declare const jQuery: (selector: string) => any;
declare const $: typeof jQuery;

// 声明全局函数
declare function gtag(command: string, ...args: any[]): void;

// 扩展全局对象
declare global {
  interface Window {
    __APP_CONFIG__: {
      apiUrl: string;
      version: string;
    };
  }
}

// 声明全局类型
declare type Nullable<T> = T | null;

为 JSON / CSS / 图片等文件声明类型

// declarations.d.ts

// JSON 文件
declare module "*.json" {
  const value: any;
  export default value;
}

// CSS Modules
declare module "*.module.css" {
  const classes: Record<string, string>;
  export default classes;
}

declare module "*.module.scss" {
  const classes: Record<string, string>;
  export default classes;
}

// 图片文件
declare module "*.png" {
  const src: string;
  export default src;
}

declare module "*.svg" {
  const src: string;
  export default src;
}

// Vue 单文件组件
declare module "*.vue" {
  import type { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

7.5 @types 和 DefinitelyTyped

大多数流行的 JS 库已经有社区维护的类型声明包:

# 安装类型声明
npm install --save-dev @types/lodash
npm install --save-dev @types/node
npm install --save-dev @types/express
npm install --save-dev @types/react

# 之后就能获得完整的类型提示
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2); // ✅ 有完整的类型提示

查看是否需要 @types

  1. 库自带类型声明(package.json 中有 types 字段)→ 不需要
  2. 需要单独安装 @types/xxx → 去 npmjs.com 搜索
  3. 都没有 → 自己写声明文件

7.6 三斜线指令

用于引入其他声明文件(在现代项目中较少使用):

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

💡 现代项目通常通过 tsconfig.jsontypes 字段来管理,很少直接用三斜线指令。


7.7 命名空间(Namespace)

命名空间是 TypeScript 早期的模块化方案,现在主要用于声明文件中组织类型:

// 在声明文件中常见
declare namespace Express {
  interface Request {
    user?: {
      id: string;
      name: string;
    };
  }
}

// 也用于组织大量类型
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }

  export class EmailValidator implements StringValidator {
    isValid(s: string): boolean {
      return s.includes("@");
    }
  }
}

const validator: Validation.StringValidator = new Validation.EmailValidator();

💡 新项目中不推荐使用命名空间来组织代码,应使用 ES Module。命名空间主要用在声明文件中。


7.8 模块解析策略

TypeScript 有多种模块解析策略,由 tsconfig.json 中的 moduleResolution 控制:

{
  "compilerOptions": {
    "moduleResolution": "bundler" // 推荐:适用于 Vite、webpack 等
    // 其他选项:
    // "node"      - Node.js 传统解析
    // "node16"    - Node.js 16+ ESM 解析
    // "nodenext"  - 最新 Node.js 解析
  }
}

路径别名

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

⚠️ 路径别名只在 TypeScript 编译时生效,打包工具(Vite/webpack)也需要对应配置。


📝 练习

  1. 创建一个模块 math.ts,导出 addsubtractmultiplydivide 函数和一个 MathOperation 类型
  2. 创建 index.ts 作为桶文件,重新导出所有内容
  3. 为一个假想的 analytics 库写声明文件
  4. 声明全局的 __DEV__ 变量(boolean 类型)
// 参考答案

// 1. math.ts
export type MathOperation = (a: number, b: number) => number;

export const add: MathOperation = (a, b) => a + b;
export const subtract: MathOperation = (a, b) => a - b;
export const multiply: MathOperation = (a, b) => a * b;
export const divide: MathOperation = (a, b) => {
  if (b === 0) throw new Error("除数不能为 0");
  return a / b;
};

// 2. index.ts
export { add, subtract, multiply, divide } from "./math";
export type { MathOperation } from "./math";

// 3. analytics.d.ts
declare module "analytics" {
  interface AnalyticsConfig {
    trackingId: string;
    debug?: boolean;
  }

  export function init(config: AnalyticsConfig): void;
  export function track(event: string, data?: Record<string, any>): void;
  export function identify(userId: string): void;
}

// 4. global.d.ts
declare const __DEV__: boolean;