01 起步与工具链
TypeScript 6.0 的安装、五种运行方式、tsc 命令行全表与诊断参数
10 分钟阅读
这不是教程,是字典。教程教你走路,字典只在你卡住时递上一根拐杖。所以本页写得密、写得短、写得没什么耐心——每一条都假设你已经会编程,只是记不清 TypeScript 里这个动作该怎么写。
风格上参考 Rust Language Cheat Sheet,但内容完全按 TypeScript 重写:TS 的类型系统是图灵完备的、类型在运行时完全不存在、结构化类型带来了一堆「看起来能过、其实错得离谱」的兼容,还有一整套只存在于类型层面的编程技巧。照搬没有意义,该不一样的地方就让它不一样。
版本基线:TypeScript 6.0.3(本机
tsc --version实测),示例默认按 TS 6.0 的新默认值编写——strict已默认开启、target已默认ES2025。凡 TS 5.x 才引入的特性标 🆕,凡 6.0 中已废弃的写法标 🗑️ 并给出替代方案。
TypeScript 6.0 是最后一个基于 JavaScript 代码库的版本,也是通往 7.0(Go 重写的原生编译器)的桥梁。它的主要目的就是对齐 7.0 行为,因此大量 5.x 时期的惯用配置在 6.0 里已经报废弃错误(TS5101 / TS5107),到 7.0 会彻底失效。
本速查表所有涉及「默认值」和「废弃项」的断言,都在 TS 6.0.3 上实际跑过,不是抄来的。
| 符号 | 含义 |
|---|---|
| 🔥 | 高频使用,值得先记住 |
| ⚠️ | 陷阱或易错点,踩过一次就该记住 |
| 🛑 | 错误示例,故意写错给你看 |
| 🆕 | TS 5.x 引入的特性,在 6.0 中可用 |
| 🗑️ | 在 6.0 中已废弃,7.0 将移除;标了替代写法 |
| 🚧 | 有限制、仍在演进,或需要额外开关 |
| 🝖 | 偏深的内容,第一遍可以跳过 |
| 💭 | 笔者见解,不是官方定论 |
| ↪ | 等价写法或语法糖展开 |
| 📘 | 指向官方文档或权威资料 |
关于
// ✅/// ❌注解:它们标的是编译器实际行为。标// ❌ TS2322:的地方写的是真实错误码,可以在 09 诊断与错误码 里反查。注释使用中文,但代码标识符与报错原文保持英文,方便直接搜索。
| 主题 | 内容一句话 |
|---|---|
| 01 起步与工具链 | 装什么、五种跑法、tsc 命令行全表、诊断参数 |
| 02 类型系统主干 | 原始类型全表、any/unknown/never 之辨、类型运算符、内置工具类型 |
| 03 收窄与类型守卫 | 控制流分析全表、typeof 返回值映射、自定义守卫、穷尽性检查 |
| 04 函数、对象与类 | 重载、this、函数变型表、索引签名、#私有字段 vs private |
| 05 泛型与类型推断 | 约束、条件类型、infer、映射类型、变型标注、NoInfer |
| 06 类型层编程配方 | 手写工具类型配方、递归技巧,以及什么时候该收手 |
| 07 模块系统与声明文件 | ESM/CJS 互操作、解析策略矩阵、.d.ts 编写、声明合并 |
| 主题 | 内容一句话 |
|---|---|
| 08 tsconfig 全量参考 | 按类别分表、strict 逐项拆解、6.0 废弃清单、可直接抄的配置模板 |
| 09 诊断与错误码 | 高频 TSxxxx 错误码反查表、诊断参数、如何定位类型来源 |
| 10 异步与错误处理 | Promise 组合子类型、Awaited、using、错误类型建模 |
| 11 运行时校验与边界 | 类型在运行时不存在——JSON、环境变量、API 边界怎么守住 |
| 12 测试 | 类型断言测试、mock 类型安全、各测试运行器的类型接入 |
| 主题 | 内容一句话 |
|---|---|
| 13 发布带类型的库 | exports 与 types 字段、双发布、声明产物、类型 API 的语义化版本 |
| 14 JSX 与前端类型模式 | 组件与事件类型、useRef/useState 推断陷阱、CSS 模块声明 |
| 15 从 JS 迁移与生态互操作 | 渐进迁移路线、无类型依赖处理、any 治理、lint 边界 |
| 16 血泪速查 | 按症状索引的踩坑表:你这么写 / 实际发生什么 / 正确写法 |
| 17 编译器与性能 | 编译变慢怎么查、项目引用、isolatedDeclarations、TS 7.0 迁移前瞻 |
TypeScript 的坑,九成来自没建立下面这几个模型。先把它们背下来,往后每一页都会轻松很多。
这是最重要的一条。tsc 做的所有事情——检查、推断、报错——都发生在编译期;它产出的 JavaScript 里一个类型都不剩。
| |
推论(每一条都导致过生产事故):
| 推论 | 后果 |
|---|---|
| 类型不能做运行时判断 | if (typeof x === "User") 永远不成立 🛑 |
| 类型不能校验外部数据 | fetch() 回来的 JSON 转成 User 只是你说了算 |
| 类型不影响性能 | 泛型、交叉类型、条件类型都不产生运行时开销 |
类型不能保护 any 进来的东西 | 类型断言是许愿,不是转换 |
这就是为什么本速查表专门有 11 运行时校验与边界——类型系统管不到数据入口,那一层只能靠运行时校验。💭
TS 用的是结构化类型(structural typing),也叫「鸭子类型」:只要形状对得上,就认为类型兼容,类型叫什么名字、在哪定义的都不重要。
| |
这跟 Java/C#/Swift 的名义类型(nominal typing)截然相反,好处是灵活,代价是:
| 反直觉现象 | 说明 |
|---|---|
| 两个无关类型可以互相赋值 | 只要结构兼容 |
| 少几个属性也能赋值 | 目标类型只要求「至少有哪些属性」 |
| 多出来的属性有时却报错 | 这叫多余属性检查,只对对象字面量生效,见 04 |
| 想把它们区分开很难 | 需要品牌类型(branded type)人为加一个私有标记 |
同一个名字 Foo 可以同时是类型和值,也可以只是其中之一。理解这个能解释大量「为什么这里写得那里写不得」:
| |
最典型的踩坑是 typeof 有两副面孔:
| 写法 | 位置 | 含义 |
|---|---|---|
typeof x === "string" | 表达式(值空间) | 运行时判断,返回 boolean |
type T = typeof x | 类型位置(类型空间) | 编译期取 x 的类型 |
| |
| |
res.json() 返回 any,把 any 赋给 User 不会报错,因为 any 可以赋给任何类型。运行时数据长什么样,TS 一无所知。
安全与不安全的入口对照:
| 入口 | 静态类型 | 真实安全 |
|---|---|---|
| 你自己 new 出来的对象 | ✅ | ✅ |
| 类型化库的返回值 | ✅ | ✅(取决于库) |
JSON.parse / res.json() | ❌ any | ❌ |
localStorage.getItem | ❌ string | null | ❌ |
process.env.X | ❌ string | undefined | ❌ |
| 表单输入、URL 参数 | ❌ | ❌ |
TS 的设计目标里明确写着「不追求类型系统完备性」,它优先考虑的是「能很好地描述 JavaScript 里真实存在的模式」。所以:
| 现象 | 例子 |
|---|---|
| 有安全漏洞(unsound) | 数组协变:string[] 可以赋给 (string | number)[],写进去就炸 |
| 有故意留的口子 | any、as、非空断言 !、@ts-ignore |
| 有时会误报 | 复杂泛型推断失败,需要手写类型参数 |
| 有时会漏报 | 索引访问、稀疏数组、可变性 |
对待漏洞的正确态度:知道它们在哪,用在明确知道安全的边界上,而不是当成日常工具。as 用多了,你就只是写了个带注解的 JavaScript。💭
先让第一行代码跑起来。TS 本身不能直接运行——它要么被编译成 JS,要么由运行时/工具即时擦除类型。
最原始也最可靠的路径:编译成 .js 再交给 Node 跑。
| |
只想类型检查、不要产物——这是最常用的命令,CI 里必加:
| |
好处是行为完全可控、产物可用于生产;坏处是多一步编译。
Node.js 22.18+ / 24 可以直接跑 .ts 文件:类型标注在加载时被擦除(strip),不生成 .js。
| |
实测于 Node v24.20.0:
| 语法 | 能否直接跑 | 原因 |
|---|---|---|
类型注解、interface、type | ✅ | 纯擦除 |
import type / export type | ✅ | 纯擦除 |
as 断言、非空断言 ! | ✅ | 纯擦除 |
declare 声明 | ✅ | 纯擦除 |
enum、带运行时代码的 namespace | ❌ ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | 需要转换而非擦除 |
构造函数参数属性 constructor(public a) | ❌ | 会生成运行时代码 |
要跑 enum 得换开关(会打 ExperimentalWarning):
| |
想让代码必然能被 Node 直接跑,就打开
erasableSyntaxOnly:它会让tsc主动报错禁止所有非擦除语法(错误码TS1294)。这是 5.8 引入、为「Node 原生跑 TS」时代准备的开关。🚧
不想每次手动编译时用它们,改完直接跑,内部即时转译。
| |
| 工具 | 特点 | 适合 |
|---|---|---|
tsx | 快、零配置、ESM/CJS 都能跑 | 本地开发、脚本 🔥 |
ts-node | 走完整 tsc 类型检查,配置项多 | 需要类型检查后再跑 |
node --watch | Node 自带热重启 | 配合上面任一使用 |
⚠️ 两者都可能不做类型检查(
tsx默认不检查)。类型错误要靠编辑器或tsc --noEmit兜住,别指望运行器。
现代前端和全栈项目里,TS 通常由 Vite、Next.js、Vitest 等工具的转译管线处理,你从不手动执行 tsc。
| 场景 | 谁负责转译 | 类型检查由谁做 |
|---|---|---|
| Vite / Vitest | esbuild / Rollup | 单独的 tsc --noEmit 或 vue-tsc |
| Next.js | SWC | next build 内置,或单独的 tsc --noEmit |
| Bun | Bun 内置转译 | bun run --bun tsc --noEmit |
| esbuild / SWC 单独使用 | 该工具 | 只能靠 tsc |
⚠️ 关键认知:esbuild、SWC、Bun 这类工具只擦除类型,不做类型检查。它们追求的是速度,类型错误它们压根不看。所以「构建成功」不等于「类型正确」,CI 里必须单独有一个
tsc --noEmit步骤。这是新手最常见的误解之一。
6.0 最大的变化之一是默认值大幅变严。如果你不写 tsconfig.json(或写个空的 {}),实际生效的是这些——全部在 TS 6.0.3 实测:
| 选项 | 5.x 默认 | 6.0 默认 | 影响 |
|---|---|---|---|
strict | false | true 🔥 | 隐式 any、null 检查全部开启 |
target | ES5(早期)/ 随版本浮动 | ES2025(LatestStandard) | 产物直接用现代语法 |
jsx | — | preserve | 保留 JSX 交给下游 |
types | 自动加载全部 @types/* | [] 空 ⚠️ | 不会自动加载 @types/node 了! |
types 这一条最坑:6.0 起不再自动把 node_modules/@types 下所有包塞进全局,于是升级后常见「突然找不到 process / Buffer / describe」:
| |
完整的 6.0 废弃与默认值变更清单,连同可直接抄的配置模板,都在 08 tsconfig 全量参考。
这些在 6.0 里已经报错(不是警告),必须改。报错码:TS5101(选项废弃)、TS5107(取值废弃)。
| 🗑️ 废弃写法 | 报错 | 替代 |
|---|---|---|
target: "ES5" | TS5107 | 最低 ES2015;真要 ES5 得用别的编译器 |
moduleResolution: "node" / "node10" | TS5107 | 打包器项目用 "bundler",Node 项目用 "nodenext" |
module: "amd" / "umd" / "systemjs" / "none" | TS5107 | ESM + 打包器 |
baseUrl | TS5101 | 把前缀写进每条 paths 里 |
outFile | TS5101 | 用 esbuild / Rollup / Vite 等外部打包器 |
downlevelIteration | TS5101 | 只在 ES5 产物下有意义,直接删 |
esModuleInterop: false / allowSyntheticDefaultImports: false | TS5107 | 互操作恒定开启,删掉该行 |
alwaysStrict: false | TS5107 | 所有代码恒为严格模式,删掉该行 |
import ... assert { }(含 import() 形式) | TS2880 | 改用 with { type: "json" } |
临时续命(仅作迁移过渡,7.0 会彻底移除):
| |
官方迁移信息汇总:
tsc报错里会直接给出https://aka.ms/ts6📘
| 版本 | 关键特性 | 本表位置 |
|---|---|---|
| 4.9 | satisfies 运算符 | 02 |
| 5.0 | const 类型参数、标准装饰器、extends 多配置继承 | 05 |
| 5.1 | 关联访问的推断改进、Getter/Setter 类型可不同 | 04 |
| 5.2 | using / await using 显式资源管理 | 10 |
| 5.3 | 类型收窄与 switch 的改进 | 03 |
| 5.4 | NoInfer<T>、闭包收窄改进 | 05 |
| 5.5 | 推断的类型谓词、isolatedDeclarations | 03 |
| 5.6 | 禁止可疑的内建迭代、严格内建迭代器检查 | 16 |
| 5.7 | 更长的相对路径不再被当作错误、never 初始化检查 | 09 |
| 5.8 | erasableSyntaxOnly、require() 的 ESM 支持 | 08 |
| 5.9 | 延迟导入、编辑器体验改进 | 07 |
| 6.0 | strict 默认开启、target 默认 ES2025、types 默认空、大面积废弃清理 | 08 |
| 7.0 | Go 重写的原生编译器(速度大幅提升),移除全部废弃项 | 17 |
| 你的处境 | 从哪开始 |
|---|---|
| 刚接手一个 TS 项目 | 08 tsconfig → 16 血泪速查 |
| 报了个看不懂的错 | 09 错误码反查 |
| 要写复杂类型 | 05 泛型 → 06 配方 |
接口报 any 相关警告 | 02 特殊类型 的 any/unknown 一节 |
| 要处理接口返回的数据 | 11 运行时校验 🔥 |
| 要发一个 npm 包 | 13 发布带类型的库 |
| 老 JS 项目要上 TS | 15 迁移 |
| 编译太慢 | 17 性能 |
TypeScript 6.0 的安装、五种运行方式、tsc 命令行全表与诊断参数
原始类型全表、any/unknown/never 之辨、类型运算符、组合类型与内置工具类型
控制流分析全表、typeof 返回值映射、自定义类型守卫、穷尽性检查与收窄失效的原因
函数重载与 this、函数变型表、对象类型与索引签名、类的访问控制与 #私有字段
泛型约束与默认值、推断规则、条件类型与分配律、infer、映射类型、变型标注
可直接抄用的自定义工具类型配方、递归技巧与类型体操的代价边界
ESM 与 CJS 互操作、模块解析策略矩阵、.d.ts 编写、声明合并与模块扩充
按类别分表的编译选项参考、strict 逐项拆解、TypeScript 6.0 废弃清单与可直接抄的配置模板
高频 TSxxxx 错误码反查表、诊断参数、如何定位类型来源与读懂复杂类型错误
Promise 与组合子的类型、Awaited、async 函数陷阱、错误类型建模与 using 资源管理
类型在运行时不存在——JSON、环境变量、API 边界的运行时校验方案与 schema 库对照
类型断言测试、各测试运行器的类型接入、mock 的类型安全、测试夹具与 CI 中的类型检查
package.json 的 exports 与 types 字段、双发布产物、声明生成、类型 API 的语义化版本
jsx 选项对照、组件与泛型组件写法、事件类型全表、Hook 推断陷阱与 CSS 模块声明
渐进迁移路线、无类型依赖处理、any 治理、与 lint 和构建工具的边界
按症状索引的踩坑表:你这么写 / 实际发生什么 / 正确写法
编译变慢怎么查、extendedDiagnostics 指标解读、项目引用、isolatedDeclarations 与 TS 7.0 迁移前瞻