Skip to content

tsconfig.json 完整配置指南

一句话概述:每个选项的作用、2026 推荐配置模板、项目类型对应最佳实践

tsconfig.json 是什么?

tsconfig.json 是 TypeScript 项目的配置文件,定义了编译选项、文件包含范围和项目引用关系。放在项目根目录下,tsc 命令会自动读取它。


一、2026 推荐基础配置

通用推荐(适用于大多数项目)

json
{
  "compilerOptions": {
    // ===== 类型检查 =====
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // ===== 模块 =====
    "module": "ESNext",
    "moduleResolution": "bundler",
    "verbatimModuleSyntax": true,
    "resolveJsonModule": true,

    // ===== 输出 =====
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,

    // ===== 编译优化 =====
    "skipLibCheck": true,
    "incremental": true,

    // ===== 代码质量 =====
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,

    // ===== 互操作 =====
    "esModuleInterop": true,
    "isolatedModules": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

二、类型检查选项详解

strict(必须开启)

strict: true 等价于同时开启以下所有子选项:

text
strict = true 包含:
├── strictNullChecks        → null/undefined 不能赋给其他类型
├── strictFunctionTypes     → 函数参数严格逆变检查
├── strictBindCallApply     → bind/call/apply 参数严格检查
├── strictPropertyInitialization → 类属性必须初始化
├── noImplicitAny           → 禁止隐式 any
├── noImplicitThis          → 禁止隐式 this
├── alwaysStrict            → 输出文件添加 "use strict"
└── useUnknownInCatchVariables → catch 的 e 类型为 unknown

每个子选项的作用:

typescript
// strictNullChecks
let name: string;
// name = null;    // ❌ 不能将 null 赋给 string
// name = undefined; // ❌

// strictFunctionTypes
type Handler = (e: Event) => void;
const mouseHandler = (e: MouseEvent) => {};
// const h: Handler = mouseHandler; // ❌ 参数逆变

// noImplicitAny
function fn(x) {}     // ❌ 参数 x 隐式 any
function fn(x: any) {} // ✅ 显式 any(但不推荐)

// useUnknownInCatchVariables
try { /* ... */ } catch (e) {
  // e: unknown(必须收窄后使用)
  if (e instanceof Error) {
    console.log(e.message); // ✅
  }
}

noUncheckedIndexedAccess(强烈推荐)

索引访问的返回类型自动加上 | undefined

typescript
// 开启前
const arr: string[] = ['a', 'b'];
const item = arr[10]; // string(实际是 undefined!)

// 开启后
const item = arr[10]; // string | undefined ✅ 更安全
if (item !== undefined) {
  item.toUpperCase(); // 收窄后使用
}

// 对象索引签名同理
const map: Record<string, number> = {};
const val = map['key']; // number | undefined

exactOptionalPropertyTypes(推荐)

区分"属性不存在"和"属性值为 undefined":

typescript
interface User {
  name: string;
  bio?: string; // 可选属性
}

// 开启前:bio 可以赋值为 undefined
const user1: User = { name: 'Alice', bio: undefined }; // ✅

// 开启后:bio 只能不写或赋 string
const user2: User = { name: 'Alice', bio: undefined }; // ❌
const user3: User = { name: 'Alice' };                  // ✅
const user4: User = { name: 'Alice', bio: 'hello' };    // ✅

三、模块与解析选项详解

module

指定输出的模块格式:

说明推荐场景
ESNext保持最新 ES Module 语法打包器项目(Vite、Webpack)
NodeNextNode.js 原生 ESM + CJS 双模式Node.js 项目
CommonJS输出 require/exports旧版 Node.js
ES2022ES Module(固定版本)指定最低支持版本

moduleResolution

控制 import 语句如何找到模块文件:

text
moduleResolution 选项:
├── "bundler"   → 2026 推荐,适用于 Vite/Webpack/esbuild
│                  支持 exports 字段,不强制扩展名
├── "node16"    → Node.js 16+ ESM 模式
│   "nodenext"    需要 .js 扩展名,支持 exports 字段
├── "node"      → Node.js 经典 CJS 模式(旧版)
│                  按 node_modules 查找
└── "classic"   → TypeScript 早期模式(已过时)
json
// Vite / Webpack 项目
{
  "moduleResolution": "bundler",
  "module": "ESNext"
}

// Node.js 纯后端项目
{
  "moduleResolution": "nodenext",
  "module": "NodeNext"
}

verbatimModuleSyntax(TS 5.0+,推荐)

取代 isolatedModules,强制类型导入使用 import type

typescript
// ✅ 正确
import type { User } from './types';
import { createUser } from './utils';

// 混合导入也可以
import { createUser, type User } from './user';

// ❌ 错误:类型导入必须用 import type
import { User } from './types'; // 如果 User 只是类型

为什么推荐:打包器(esbuild、SWC)只做语法转换不做类型分析,import type 让它们知道哪些导入可以安全移除。

paths(路径别名)

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

注意paths 只影响 TypeScript 的类型解析,不影响运行时。需要配合打包器(Vite 的 resolve.alias、Webpack 的 resolve.alias)或 tsconfig-paths 包。


四、输出选项详解

target

控制输出的 JavaScript 版本:

关键特性推荐场景
ES2022Top-level await、class fields、#private现代浏览器 / Node 18+
ES2020BigInt???.兼容较旧环境
ESNext最新标准输出交给打包器处理
ES5无箭头函数、无 class极老浏览器(不推荐)

lib

声明项目可用的全局 API 类型:

json
{
  "lib": [
    "ES2022",        // ES2022 标准库(Promise、Map 等)
    "DOM",           // 浏览器 DOM API
    "DOM.Iterable",  // DOM 集合的迭代器
    "WebWorker"      // Web Worker API(如需要)
  ]
}

declarationdeclarationMap

json
{
  "declaration": true,     // 生成 .d.ts 类型声明文件
  "declarationMap": true,  // 生成 .d.ts.map,IDE 可跳转到源码
  "emitDeclarationOnly": true // 只生成 .d.ts,不生成 .js(库开发常用)
}

sourceMap

json
{
  "sourceMap": true,         // 生成 .js.map
  "inlineSourceMap": false,  // 不内联到 .js 中
  "inlineSources": false     // 不将源码嵌入 sourcemap
}

五、编译优化选项

skipLibCheck(推荐开启)

跳过 node_modules.d.ts 文件的类型检查:

  • 优点:大幅加速编译(某些项目提升 50%+)
  • 缺点:可能漏掉第三方库的类型错误
  • 实践:几乎所有项目都应该开启,第三方库的类型问题不是你的责任

incremental

json
{
  "incremental": true,
  "tsBuildInfoFile": "./dist/.tsbuildinfo"
}

开启后 TypeScript 会生成 .tsbuildinfo 文件记录编译状态,下次编译只处理变更的文件。

项目引用(Project References)

json
// tsconfig.json(根项目)
{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/ui" },
    { "path": "./packages/utils" }
  ],
  "files": []  // 根项目不直接编译文件
}

// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,    // 子项目必须开启
    "declaration": true,  // composite 要求开启
    "outDir": "./dist"
  },
  "references": [
    { "path": "../utils" } // 声明依赖
  ]
}

使用 tsc --buildtsc -b)编译,自动处理依赖顺序和增量编译。


六、项目类型配置模板

React + Vite 项目

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "jsx": "react-jsx",
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src", "vite-env.d.ts"]
}

Next.js 项目

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "jsx": "preserve",
    "plugins": [{ "name": "next" }],
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "incremental": true,
    "noEmit": true,
    "esModuleInterop": true,
    "allowJs": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Node.js 后端项目

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "NodeNext",
    "moduleResolution": "nodenext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

npm 库开发

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "verbatimModuleSyntax": true,
    "declaration": true,
    "declarationMap": true,
    "emitDeclarationOnly": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

七、常见问题

isolatedModules vs verbatimModuleSyntax

  • isolatedModules:TS 4.x 时代的方案,确保每个文件可以独立编译
  • verbatimModuleSyntax:TS 5.0+ 的替代方案,更严格也更清晰
  • 推荐:新项目用 verbatimModuleSyntax,同时可以保留 isolatedModules 兼容旧工具

jsx 选项

输出场景
react-jsx使用 _jsx 自动导入(React 17+)React + Vite/Webpack
preserve保留 JSX 不转换Next.js(由框架处理)
reactReact.createElement(旧版)React 16 及以下
react-jsxdev开发模式带调试信息开发环境

noEmit vs emitDeclarationOnly

  • noEmit: true:不输出任何文件,只做类型检查(前端项目常用,JS 输出交给 Vite/Webpack)
  • emitDeclarationOnly: true:只输出 .d.ts,不输出 .js(库开发常用,JS 输出交给 SWC/esbuild)

面试高频题

1. strict: true 包含哪些子选项?为什么必须开启?

答案:包含 strictNullChecksstrictFunctionTypesstrictBindCallApplystrictPropertyInitializationnoImplicitAnynoImplicitThisalwaysStrictuseUnknownInCatchVariables 共 8 个子选项。必须开启是因为这些检查能在编译期捕获大量潜在 bug(空值访问、参数类型错误、隐式 any 等),是 TypeScript 类型安全的基础。

2. moduleResolution: "bundler""node16" 的区别?

答案bundler 适用于前端打包器环境(Vite/Webpack),支持 package.jsonexports 字段但不强制文件扩展名。node16/nodenext 适用于 Node.js 原生 ESM,要求导入时写 .js 扩展名,严格遵循 Node.js 的模块解析规则。前端项目用 bundler,纯 Node.js 后端用 nodenext

3. skipLibCheck 会有什么风险?

答案:跳过第三方库 .d.ts 文件的类型检查,可能漏掉第三方库之间的类型冲突或版本不兼容问题。但实际风险很小,因为:(1)主流库的类型定义已经过充分测试;(2)你的项目代码仍然会被完整检查;(3)编译速度提升明显(大型项目可提升 50%+)。几乎所有项目都应该开启。

4. verbatimModuleSyntax 的作用是什么?

答案:强制类型导入使用 import type 语法。这让 esbuild、SWC 等只做语法转换的工具知道哪些导入是纯类型(可安全移除),哪些是运行时需要的值。取代了旧版的 isolatedModules + importsNotUsedAsValues 组合,语义更清晰。