CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
TypeScript 核心/课时/模块与 tsconfig
RawBlame
课时目录12 节

TypeScript 核心

  1. 01TypeScript 是什么
  2. 02基本类型
  3. 03函数类型
  4. 04接口与类型别名
  5. 05联合与交叉类型
  6. 06类型收窄
  7. 07泛型
  8. 08工具类型
  9. 09模块与 tsconfig
  10. 10React 组件 typing
  11. 11异步与 API 类型
  12. 12综合练习与 Next.js

模块与 tsconfig

ESM、tsconfig.json、strict 选项。

30 分钟

模块与 tsconfig

这一课你将能做到

  1. 用 ES Module import / export 组织代码,区分类型导入和值导入
  2. 配置一份适合现代项目的 tsconfig.json,说清每个关键字段的作用
  3. 配置路径映射 @/ 别名,并在构建工具里同步生效

为什么要学

模块系统是大型项目的「文件管理系统」。没有模块,所有代码写在一个文件里,改一行就要理解全部。有了模块,每个文件只关心自己的输入(import)和输出(export),职责清晰、依赖透明。

而 tsconfig.json 是 TypeScript 项目的「总控台」:编译目标、模块规范、严格程度、路径别名,全在这里定。项目里 strict: true 到底开了什么?paths 别名为什么 @/ 指向 src?答案都在这一课。

先建立图像

模块化的核心概念:

┌──────────────┐         ┌──────────────┐
│   user.ts    │  export │   app.ts     │
│ 导出:User    │────────▶│ 导入:User   │
│ 导出:format │  import │ 使用:User   │
└──────────────┘         └──────────────┘

类型和值都可以被导出/导入
编译时 TS 检查类型,构建时模块被打包成浏览器/Node 能执行的格式

tsconfig.json 的决策路径:

你的 .ts 源码
    │
    ├── target: ES2020? ES2015? ES5?    (编译成什么版本的 JS)
    ├── module: ESNext? CommonJS?        (用什么模块规范)
    ├── strict: true? false?             (严格程度开关)
    ├── paths: @/* → src/*?              (路径别名)
    └── outDir: dist/?                   (产物目录)

跟着做

新建项目文件夹,逐步实操。

步骤 1:创建项目结构

mkdir module-demo
cd module-demo
mkdir src

创建 src/types.ts:

export interface User {
  id: number;
  name: string;
  email: string;
}

export type Role = "admin" | "editor" | "viewer";

创建 src/user.ts:

import type { User } from "./types";

export function createUser(name: string, email: string): User {
  return { id: Date.now(), name, email };
}

export function formatUser(user: User): string {
  return `#${user.id} ${user.name} <${user.email}>`;
}

创建 src/app.ts:

import { createUser, formatUser } from "./user";

const user = createUser("小明", "ming@example.com");
console.log(formatUser(user)); // #... 小明 <ming@example.com>

编译运行:

tsc --init
tsc
node dist/app.js

步骤 2:导出方式对比

// 方式一:具名导出(named export)——推荐
export interface User { id: number }
export function format() {}

// 导入时要写同名(可起别名)
import { User, format as fmt } from "./user";

// 方式二:默认导出(default export)——一个文件最多一个
export default function main() {}

// 导入时任意起名
import main from "./main";

规则:一个模块最好用一种风格。React 组件常用 export default,工具函数常用 export function。混用会让 IDE 自动导入混乱。

步骤 3:类型导入与值导入

// 类型导入:编译后消失,运行时不存在
import type { User } from "./types";

// 值导入:运行时真的加载这个模块
import { createUser } from "./user";

// 混合:type 关键字内联标注
import { createUser, type User } from "./user";

为什么区分?TS 编译成 JS 时,类型导入会被完全删除;值导入会保留为 require 或 import 语句。如果工具类型只被用作类型,不写 type 会导致运行时加载一个实际上不存在的文件,构建工具可能报错。

步骤 4:tsconfig.json 核心配置详解

{
  "compilerOptions": {
    // 编译目标:产出什么版本的 JS
    "target": "ES2020",

    // 模块规范:ESNext(现代浏览器/打包工具)或 CommonJS(Node)
    "module": "ESNext",

    // 严格模式总开关(推荐 true)
    "strict": true,

    // 模块解析策略
    "moduleResolution": "bundler",

    // 输出目录
    "outDir": "./dist",

    // 源码根目录
    "rootDir": "./src",

    // 允许导入 JSON 文件
    "resolveJsonModule": true,

    // 生成声明文件(发布库时必需)
    "declaration": true,

    // 路径映射(简化深路径导入)
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*"]
}

逐项解读:

字段作用推荐值
target编译后的 JS 语法版本"ES2020" 或 "ES2022"
module模块系统规范浏览器用 "ESNext",Node 用 "CommonJS"
strict严格模式总开关永远 true
moduleResolution解析策略现代项目用 "bundler"
outDir编译产物目录"./dist"
rootDir源码根目录"./src"
paths路径别名"@/*": ["./src/*"]

步骤 5:路径别名实战

配置前(深层嵌套的噩梦):

// src/components/ui/Button.tsx
import { theme } from "../../styles/theme";
import { utils } from "../../../lib/utils";

配置 paths 后:

import { theme } from "@/styles/theme";
import { utils } from "@/lib/utils";

注意:paths 只影响 TS 的类型检查和编译,运行时/打包工具不认识。需要在构建工具里同步配置:

  • Vite:vite.config.ts 里配 resolve.alias
  • Webpack:resolve.alias
  • Node:tsc 编译后路径不生效,需要 tsconfig-paths 或打包时处理

步骤 6:Node.js 项目的特殊配置

如果写 Node 后端而不是前端:

{
  "compilerOptions": {
    "module": "CommonJS",
    "moduleResolution": "Node",
    "target": "ES2022",
    "strict": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  }
}

关键差异:

  • module: "CommonJS":Node 的传统模块规范(require / module.exports)
  • esModuleInterop: true:让 import express from "express" 能正确编译成 require("express"),不加会报 express_1.default is not a function

步骤 7:声明文件与类型导入

发布一个库时,要生成 .d.ts 声明文件:

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./types",
    "emitDeclarationOnly": true
  }
}

生成的 types/user.d.ts 只保留类型信息,不含实现,供其他 TS 项目引用。

讲清楚要点

  1. ESM 是未来,CJS 是过去:浏览器和现代打包工具全面转向 ESM;Node 18+ 也原生支持 ESM。新项目用 ESM,老项目(CommonJS)逐步迁移。
  2. import type 是零成本的:它只参与类型检查,编译后完全消失。养成习惯:只引入类型的文件用 import type。
  3. paths 是编译期概念:TS 检查类型时认识 @/,但 Node/浏览器不认识。打包工具必须同步配置,否则运行时模块找不到。
  4. strict 不是可选项:它包含 strictNullChecks、noImplicitAny 等 8 个子开关,关掉其中任何一个都会漏掉一类线上 bug。
  5. 模块解析是构建工具的事:tsc 只负责类型检查和编译成 JS,真正打包、tree-shaking、路径替换是 Webpack/Vite/Rollup 的工作。
  6. 声明文件是库的入口:import 一个库时 TS 先找 .d.ts 文件,找不到就会报「找不到模块或其类型声明」。

你可能会懵的地方

  • import { type User } 和 import type { User } 有区别吗? 效果相同,前者是 TS 4.5+ 的内联写法,后者是整体声明。前者适合混合导入(值 + 类型),后者适合纯类型导入。

  • esModuleInterop 是干嘛的? 解决 ESM 和 CJS 互操作的问题。没有它,import express from "express" 编译成 CJS 时会变成 express_1.default(),但 express 包导出的是 module.exports = express,没有 .default,导致运行时报错。

  • paths 别名为什么打包后不生效? paths 只在 TS 编译阶段起作用,tsc 输出的是相对路径。运行时/打包阶段需要构建工具(Webpack/Vite)单独配置 alias,或者使用 tsconfig-paths 让 Node 也认识。

  • 一个文件能同时有 export default 和 export 吗? 能,但不推荐。混合导出会让自动导入和 IDE 提示变得混乱,一个模块最好只选一种风格。

  • module: "ESNext" 和 module: "ES2022" 有区别吗? ESNext 跟随最新标准,ES2022 固定到 ES2022 特性。建议用 ESNext 或 ES2022,差别不大,构建工具会再处理一遍。

错法对照

错法问题正解
循环依赖 a.ts → b.ts → a.ts运行时可能拿到未初始化的值提取公共逻辑到第三个模块打破循环
import * as _ from "lodash" 全量导入打包体积爆炸按需导入 import { debounce } from "lodash-es"
只配 TS 的 paths 不配打包工具 alias运行时报模块找不到Webpack/Vite 里同步配 alias
在 Node 项目用 module: "ESNext" 不加额外配置tsc 编译出浏览器 ESM,Node 跑不了Node 项目用 module: "CommonJS"
以为 strict: false 是「宽松模式」8 个严格检查全关,类型系统形同虚设永远 strict: true,有报错改代码

卡点急救

  1. 报错 Cannot find module '@/utils':打包工具 alias 没配。检查 vite.config.ts 或 webpack.config.js 的 resolve.alias。
  2. tsc 编译后 dist 里没有 .d.ts 文件:declaration 没开或配置没生效。检查 tsconfig.json 的 declaration 字段。
  3. Node 运行报错 exports is not defined:编译成了 ESM 但 Node 按 CJS 加载。改 module: "CommonJS" 或把项目配成 ESM(package.json 加 "type": "module")。
  4. 类型导入在构建后报错:构建工具不认识 import type。确保工具链支持 TS(Vite 内置、Webpack 配 babel/ts-loader)。

面试常问

  • ES Module 和 CommonJS 的核心区别? ESM 是静态的(编译时确定依赖,支持 tree-shaking),CJS 是动态的(运行时加载)。ESM 的 import 是声明式的,CJS 的 require 是函数调用。现代前端全面转向 ESM。

  • import type 的意义是什么? 显式标记「只引入类型」。编译后完全消失,减少运行时依赖;让打包工具知道可以安全地 tree-shake 掉这个导入;避免「值被误删导致的运行时错误」。

  • paths 别名在运行时为什么失效? paths 只在 TS 编译阶段生效,tsc 编译成 JS 时会转成相对路径。运行时环境(Node/浏览器)不认识 @/,需要构建工具在打包时做路径替换。

  • 什么是声明文件(.d.ts)? 只包含类型定义、不含实现的文件。作用:让 TS 项目引用 JS 库时获得类型提示;发布库时供使用方获得类型支持。

自测三问

  1. import { type User } 和 import type { User } 有什么细微差别?
  2. 为什么 paths 配置了 @/,Webpack 打包后运行时报 Cannot find module?
  3. esModuleInterop: true 解决的具体是什么问题?如果不加,import express from "express" 编译后会发生什么?

小结一句话

ESM 模块系统管代码组织,tsconfig.json 管编译行为;import type 做零成本类型引用,paths 简化导入路径但需构建工具同步——模块和配置是大型 TS 项目的地基。

延伸

  • Handbook: Modules
  • tsconfig 官方参考
  • 下一课:React 组件 typing——props、state、事件、ref、children、泛型组件的完整类型写法。
加载本节练习…
← 上一课工具类型下一课 →React 组件 typing