讲解

TypeScript 完全采用 ES 模块(import/export)作为模块方案,古老的 namespace 和 /// 只存在于老代码和 .d.ts 里。每个有顶层 import/export 的文件就是一个模块,模块内的声明默认私有,export 才暴露。类型和值一样参与导出导入:export interface User、import { User } from "./types"。

类型导入有两个专用语法值得掌握。import type { User } 声明「这只是类型导入」,编译后整个导入被擦除,不会留下运行时依赖——在跨层引用(比如前端引后端的类型定义)时避免意外引入运行时代码。内联写法 import { type User, createUser } 可以在一条语句里混用。开启 verbatimModuleSyntax(越来越多的模板默认开启)后,类型导入必须显式标 type,这个习惯建议从现在开始养成。

导出侧的 re-export(export { x } from "./a"、export * from "./a")用于做 barrel 文件(index.ts 统一出口);export type * from 则只转发类型。模块解析由 tsconfig 的 moduleResolution 决定:现代项目用 bundler(配合打包器,扩展名可省)或 nodenext(纯 Node ESM,相对导入必须写 .js 扩展名——指的是编译后的文件名,这是新手最困惑的点)。本教程示例统一用 bundler 解析。

示例

下面是一个多文件程序,本章的四个块会被一起编译。先定义类型模块(file: 约定标记文件名):

// file: models.ts
export interface Task {
  id: number;
  title: string;
  done: boolean;
}

export type TaskFilter = "all" | "active" | "done";

再是实现模块,注意 import type 的使用:

// file: store.ts
import type { Task, TaskFilter } from "./models";

export function filterTasks(tasks: Task[], filter: TaskFilter): Task[] {
  if (filter === "all") {
    return tasks;
  }
  return tasks.filter((t) => (filter === "done" ? t.done : !t.done));
}

export function countDone(tasks: Task[]): number {
  return tasks.filter((t) => t.done).length;
}

barrel 文件统一出口:

// file: index.ts
export type { Task, TaskFilter } from "./models";
export { filterTasks, countDone } from "./store";

使用方只依赖 barrel:

// file: main.ts
import { filterTasks, countDone, type Task } from "./index";

const tasks: Task[] = [
  { id: 1, title: "学泛型", done: true },
  { id: 2, title: "写教程", done: false },
];

console.log(`待办 ${filterTasks(tasks, "active").length} 项,已完成 ${countDone(tasks)} 项`);

常见坑

  • Node ESM 下忘写 .js 扩展名:moduleResolution 为 nodenext 时,import "./store" 报错,要写 import "./store.js"——指向编译产物,不是写错。
  • 类型导入造成循环依赖:A 引 B 的值、B 引 A 的类型,标成 import type 就能解开运行时环。
  • barrel 文件滥用导致打包膨胀:export * 的树摇依赖打包器能力,敏感项目直接深路径导入。
  • 混用 CommonJS 与 ESM 心智:require/module.exports 与 import/export 的类型行为有差异(esModuleInterop),新项目一律 ESM。

小结

ES 模块即 TypeScript 模块;import type 避免运行时依赖;barrel 统一出口;解析策略看 moduleResolution(bundler 或 nodenext)。下一章讲纯类型文件:.d.ts 声明文件。