TypeScript 是什么:为什么大项目都在用它
读完这篇你能做两件事:① 用三句话向别人解释 TypeScript 是什么、它到底防住了哪一类事故;② 在同事或 AI 说「上 TypeScript 就安全了」「把 .js 改成 .ts 就行」时,知道该追问哪几句、该看哪几行配置。它卖的不是「更少的 bug(程序里的错误)」,而是改动时更早知道你碰坏了什么——项目越大、人越多、活得越久,这份保险越值钱。
📅 更新于 2026-09-23 · 本文校对于 TypeScript @ 7.0.2(7.0 于 2026-07-08 发布)· 官方源:github.com/microsoft/TypeScript/releases
typescript 这个工具的次数(2026-09-15 ~ 09-21,本文自取)0030 秒速览:三句话,和一个你今天就能做的动作
如果只记三句话
① 它是 JavaScript 加了一层「会消失的说明书」。你写代码时,这层说明书帮你核对(这里该放数字,不是文字);程序真正跑起来的那一刻,它已经没了。TypeScript 官方把这个过程叫擦除类型,所以它绝不会因为类型而改变程序的行为。
② 它挡的不是「手滑打错字」,是「改了一处、忘了另一处」。项目越大、参与的人越多、活得越久,「改动」发生的次数就越多,这份保险就越值——这才是大项目都在用它的真正原因,不是因为它更时髦。
③ 「用了 TypeScript」和「检查过类型」是两件事。很多工具只把类型删掉、根本不检查;Node.js 现在能直接跑 .ts 文件,但官方文档明确写着它不做类型检查。所以判断一个项目时,要问的永远是那句:哪一步真的检查了?
接下来 60 秒:你的第一个动作
打开 TypeScript 官方的在线试验场(叫 Playground,意思是「操场」,在浏览器里就能改代码、立刻看到检查结果,不用装任何东西):typescriptlang.org/play。
右边默认的示例代码不重要——把它整段清空,粘贴下面这八行(它就是 §03 要讲的例子):
const order = { price: 35, count: 3 };
function finalPrice(price: number, count: number) {
const total = price * count;
return total >= 100 ? total - 20 : total;
}
console.log(finalPrice(order.prcie, order.count) + " 元");
然后做三个动作:① 找到那个红色的波浪线,鼠标停在上面读那句英文;② 把 prcie 改成 price,看红线消失;③ 把最后一行的 35 改成 "35"(加英文双引号,让它变成文字),看新的红线出现。
这 60 秒买到的不是「学会写 TypeScript」,而是亲眼看到它的检查长什么样:它不是运行时报错,而是在你写的时候就把问题标出来。整篇文章讲的都是这一件事的延伸。
只需要一个浏览器,上面那个动作就能做。如果你想在自己电脑上跑命令(§03 的「路 B」),需要一个已经装好 Node.js 的目录——没有也没关系,那一段可以先整段跳过,本篇其余部分不依赖它。
Node.js 是什么:一个让 JavaScript 代码能在你电脑上(而不是只在浏览器里)运行的程序。它是本篇反复出现的两个「运行环境」之一,另一个是浏览器。而 npm 是随 Node.js 一起装上的「包商店」——开发者从那里下载别人写好的代码包,本篇后面出现的 npm install 就是从那儿取东西。
场景索引:你现在的状态 → 直接看哪一节
| 你现在的状态 | 直接跳到 | 为什么先看这节 |
|---|---|---|
| AI 说「我给它加了类型」,你不知道这句话值不值钱 | §07 | 那里有一张「危险信号」表和一条可以让 AI 自证的验收要求 |
| 想知道它到底值不值、为什么大家嘴里都是它 | §05 → §06 | 先看它挡住哪四类事故,再看它的五笔成本;两边都看完才叫判断 |
手上有一份 tsconfig.json,想知道哪几行是关键 | §08 | 只讲七个开关和一个「纪律证据」 |
| 听人说「Node 现在直接跑 TS 了」「TS 7 快 10 倍」 | §09 | 这三句话都没错,但说的不是一回事;混起来会得出错误结论 |
| 想先看一个具体例子,再回头看定义 | §03 | 一个拼错的字段名,牵出三类不同的问题 |
01一句话定义:JavaScript 加一层会消失的说明书
你可能已经见过这个场景:让 AI 写一个功能,它交回来的文件名有时是 order.js,有时是 order.ts。那个多出来的 t,就是 TypeScript。
它到底多做了什么?官方的定义是两句话,合起来正好说清这件事:
“TypeScript is a language that is a superset of JavaScript: JS syntax is therefore legal TS.”——TypeScript 是一种 JavaScript 的超集:JavaScript 的写法在 TypeScript 里全都合法。
“TypeScript is JavaScript’s runtime with a compile-time type checker.”——TypeScript 就是「JavaScript 的运行环境,外加一个在编译时工作的类型检查器」。
出处:typescriptlang.org 官方手册 TypeScript for the New Programmer(本文 2026-09-23 抓取)。
用日常语言说一遍:JavaScript 原封不动地留着(包括它怎么跑、跑出什么结果),只是在旁边多贴了一层写给「检查器」看的说明书。说明书的内容只有一件事:某个位置该放什么种类的值——数字还是文字?这个对象该有哪几个字段、每个字段是什么种类?
「超集」是什么意思:不是另一种语言,是多了一层
很多人第一次听到「超集」会以为它是一门像 Python 那样「另外的语言」。不是。两句话就能分开:
- 任何能跑的 JavaScript,都是合法的 TypeScript。官方原文就是这么写的。你把一个
.js文件直接改名成.ts,语法上不会报错。 - 反过来不成立。TypeScript 多出来的那部分,就是「说明书」语法本身:这个值是数字、这个对象必须有这几个字段、这个函数(一段有名字、可以被反复调用的代码)只能接收文字。
一个还算贴切的类比:这份说明书像快递单上多印的一栏「此处必须填数字」。它不改变包裹里装的东西,只是在有人写错的时候把他拦下来。
这个类比到哪里就不准了:快递单填错,包裹会被拒收;而 TypeScript 的「拦下」不是拒绝运行。一份有类型错误的代码,只要语法本身没坏,照样能跑起来——只是没人帮你检查过。这一点在 §02 和 §06 都会再出现,它是理解 TypeScript 边界的关键。
三个最常被误认的东西(先把「它不是什么」说清)
误认一:它是一门新语言,像 Python 之于 JavaScript
不是。它没有自己的运行环境。把类型那层擦掉之后,剩下的就是普通 JavaScript,由浏览器或 Node.js 执行。你不需要「换一台发动机」,只是在发动机外面加了一道出厂检查。
误认二:浏览器认识 .ts 文件
不认识。浏览器和 Node.js 的执行引擎,历史上只认 JavaScript(Node.js 从 2024 年起加了一个例外,见 §09,但那个例外也不做检查)。所以 .ts 文件在被执行之前,通常要先经过一步「翻译」——把说明书那层去掉,转成 .js。这一步叫编译(也叫构建),干这活的程序叫编译器。TypeScript 自带的编译器命令是 tsc。
误认三:「用了 TypeScript」等于「更安全」
准确的说法是:它让某一类错误更早暴露。它保证的是「形状对得上」,不保证「意思对」。把「满 100 减 20」写成「满 100 减 200」,类型检查全程绿灯——因为类型完全没错,错的是意思。§03 会给你一个这样的例子。
那层说明书里到底写了什么:四个词
后面全篇都会用到这四个词,先在这儿一次说清,一个都不留到术语表里去解释。
| 词 | 用日常语言说 | 什么时候你会听到它 |
|---|---|---|
| 类型(type) | 一个位置该放什么种类的值:数字、文字、真假、一组固定字段的对象 | 「这个参数的类型是数字」=「这里只能放数字」 |
| 类型注解(annotation) | 你手写在代码里的那句声明,比如 price: number(意思是 price 这个位置放数字) | AI 说「我给关键地方都补了注解」 |
| 类型推断(inference) | 你没写,检查器自己从上下文猜出来。写 const price = 35,它就知道这是数字 | 「大部分地方不用手写,它会自己推」 |
| 类型检查(type checking) | 拿这些声明去核对每一处使用,对不上就报错。这是 TypeScript 真正干活的动作 | 「CI 里加了类型检查」 |
最关键的一句:类型只在「检查」的时候存在
这是全篇最需要记住的机制。看官方原文:
“Roughly speaking, once TypeScript’s compiler is done with checking your code, it erases the types to produce the resulting ‘compiled’ code. This means that once your code is compiled, the resulting plain JS code has no type information.”
大意:编译器检查完之后,会把类型擦掉,产出的是一份没有任何类型信息的普通 JavaScript。
由此推出三件事,它们会解释你以后遇到的大部分困惑:
- 运行时不检查。用户在输入框里填了文字,程序里那个位置声明的却是数字——运行时不会有任何拦阻,因为那时类型已经不存在了。
- 它不会让程序变慢,也不会让程序变快。类型不进入产物,运行时开销为零。同时它也不解决性能问题,那和类型无关。
- 「类型对了」不能替代「跑一遍」。类型保证的是形状,逻辑、数据来源、用户行为都要靠别的办法验(§03 会给对照)。
02它怎么运作:两个动作、两个时刻,和一个很多人漏掉的区分
TypeScript 干活只有两个动作,但这两个动作可以分开、也必须分清:
注意动作一和动作二的顺序:先检查,再擦除。检查的结果就是一堆报错(或者没有报错)。擦除的结果才是能跑的文件。
动作一发生在两个时刻
时刻一:你打字的时候(编辑器里)
这不是浏览器干的,是你用的编辑器在后台跑着一个「TypeScript 语言服务」——可以理解成一个随叫随到的检查器:你每敲一个字,它就把当前文件重新核对一遍,把问题标成红波浪线,还能顺手给你补全名字、跳到定义。这也是很多人喜欢 TypeScript 的真正理由:不是因为「安全」,而是因为好写。你在 §00 做的那个 60 秒动作,看到的就是它。
时刻二:提交前或合并前(命令 / CI 里)
编辑器里的红波浪线不是证据——它可以被忽略,也可能因为某人没打开编辑器而不存在。真正算数的检查,是有人在命令行里跑了一遍检查命令,或者由一台机器(CI,即「每次提交都自动跑一遍检查的服务器流程」)跑了一遍,并且结果被记录在案。这一步没有做,前面那些都只是便利,不是保障。
漏掉的区分:删类型 ≠ 检查类型
这是本篇最实用的一张表。市面上大多数工具只做「删类型」这一个动作,因为它快;而删类型不等于检查过。
| 工具 | 它到底做了什么 | 你该因此得出什么结论 |
|---|---|---|
tsc(TypeScript 自带) | 检查类型,并且可以只检查不产出文件(--noEmit,意思是「别写文件,只告诉我有没有错」) | 这是「真的检查过」的唯一来源 |
| esbuild / swc / Babel(三个常见的转译器,负责把代码「翻译」成能跑的样子) | 只把类型语法删掉,速度极快 | 完全不检查。esbuild 官方文档原话是“esbuild does not do any type checking”,并建议你另外并行跑 tsc --noEmit |
Node.js 直接跑 .ts | 把类型语法替换成空白,然后执行 | 不检查,而且会忽略 tsconfig.json(Node 官方文档原话)。细节见 §09 |
所以「我们项目用了 TypeScript」这句话本身信息量很低。真正有信息量的是后半句:「在哪台机器上、用哪条命令、检查了哪些文件」。这句话你可以直接拿去问同事或 AI,它比任何形容词都管用。
说明书从哪来:三个来源
- 1你自己手写的注解
比如
price: number。关键地方手写,剩下交给推断。 - 2检查器自己推断的
你没写它也能猜出来——这就是为什么把一个
.js改名成.ts大多数时候不会立刻报错。 - 3别人替你写好的「说明书文件」
事情的关键在这儿:很多现成的 JavaScript 库当初没写类型。社区给它们补了一份只有类型、没有实现的配套文件,扩展名是
.d.ts(d 就是 declaration,声明的意思)。装这类说明书包时,包名前面通常带@types/。
第三类的规模可以用数字感受一下:给 Node.js 这套运行环境写的说明书包 @types/node,一周被下载 3.2 亿次——比 TypeScript 本体(2.09 亿次)还多。原因不难猜:一个项目引用的库越多,为了读懂这些库而下载的说明书就越多。(两组数字均为本文 2026-09-23 从 npm 官方数据接口——也就是一个可以直接查数的网址——自取,复核命令见 §12。)
报错长什么样:一眼看懂的形态
TypeScript 的报错是「文件:行:列 + 一句英文 + 一个代号」。下面这种形态你以后会看到很多次:
# 形态示意(具体名字因代码而异)
src/某个文件.ts:9:25 - error TS2339: Property 'xxx' does not exist on type
'{ yyy: number; zzz: number; }'.
它其实在说三件事:哪一行、哪个名字不该出现、以及你大概是拼错了哪个名字。不需要读懂编程也能读出这三件事——这是本篇后面「不写代码也能判断」的基础。
你可能会想:检查是不是很慢?
曾经是,而且这是 TypeScript 被抱怨最多的地方。真实数字:Slack 工程团队反馈,他们的 CI 类型检查从 TypeScript 6 时代的约 7.5 分钟降到约 1.25 分钟(换成 TypeScript 7 之后)。为什么能快这么多、以及 2026 年的现状,放在 §09 讲,因为那属于「版本口径」的问题。
03最小例子:一个拼错的字段名,和它背后的三类问题
这段用一个真实的坏例子走完三件事:类型能挡住的、「现在碰巧能跑」的、类型根本挡不住的。三类分清了,你对 TypeScript 的期待就不会跑偏。
第一类:一份完全合法的 JavaScript
下面这段代码没有任何语法问题,它会跑起来,也不会报错:
// order.js —— 满 100 减 20 的价格计算
const order = { price: 35, count: 3 };
function finalPrice(price, count) {
const total = price * count;
return total >= 100 ? total - 20 : total;
}
console.log(finalPrice(order.prcie, order.count) + " 元");
注意最后一行里那个 order.prcie——把 price 拼错了。这是 AI 和你自己都最常犯的一类错。
它会怎么坏:不是报错,是「什么都不报,结果不对」
程序取一个不存在的字段,拿到的不是错误,而是一个特殊值 undefined(意思是「这里没有东西」)。接下来的计算是这样的:
| 步骤 | 发生了什么 | 你看到的现象 |
|---|---|---|
取 order.prcie | 这个字段不存在,得到 undefined | 没有任何提示 |
undefined * 3 | 得到一个特殊值 NaN(Not a Number,「这不是个数字」) | 仍然没有提示 |
判断 total >= 100 | 拿 NaN 去比大小,结果永远是「不成立」 | 仍然没有提示 |
| 打印结果 | 输出「NaN 元」 | 页面上出现了 NaN 元,可能几天后才被人发现 |
这就是这类 bug 讨厌的地方:它不崩、不报错、日志里干干净净,只是默默地给出一个错答案。
同一段代码改成 TypeScript:只多了三处
// order.ts —— 只把「说明书」部分补上,逻辑一行没改
const order = { price: 35, count: 3 };
function finalPrice(price: number, count: number): number { // ① 两个参数各标一次
const total = price * count;
return total >= 100 ? total - 20 : total; // ② 返回值标 number(这一处其实可以省,它会自己推断)
}
console.log(finalPrice(order.prcie, order.count) + " 元"); // ③ 这一行没改,但它现在会被检查
运行检查后,TypeScript 会直接说:
order.ts:9:25 - error TS2551: Property 'prcie' does not exist on type
'{ price: number; count: number; }'. Did you mean 'price'?
翻译成人话:「prcie 这个东西不存在;按你上面写的,这个对象只有 price 和 count 两个字段;你是不是想写 price?」——而且这句话出现在程序还没运行的时候。
请注意:报错的位置在调用那一行(第 9 行),而错误的根源在更早的地方(你对 order 的定义)。这类「一处定义、多处使用」的错误正是大项目的日常,也是类型检查最值钱的地方。
第二类:不改文件名,只把数据来源换掉
看第二种坏法。假设这份价格不是写死的,而是从一个网页表单里读来的——表单里读出来的东西永远是文字(用户输入的就是一串字符)。于是有人这么调用:
finalPrice("35", 3)
在普通 JavaScript 里,这行代码算得出正确答案:JavaScript 会自动把文字 "35" 当成数字 35 来乘,结果 105,减 20 得 85。看起来一切正常。
但这是「碰巧能跑」。只要有人把乘号换成加号,"35" + 3 得到的是文字 "353",而不是 38——同一份数据,换个运算符,结果天差地别,而且不报错。
TypeScript 对上面那一行的回答是:
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
人话:「这个位置你声明了要数字,却传了文字进来。」值得记住这个区别——第一类错误「现在就坏了」,第二类错误「现在还活着,但随时会坏」。后者才是类型检查真正无法替代的地方,因为测试常常测不到它。
第三类:类型挡不住的(这部分同样重要)
把逻辑改坏一点点:折扣从减 20 改成减 200。
return total >= 100 ? total - 200 : total; // 类型全对,检查全绿,结果是 -95
TypeScript 一句话都不会说。price 是数字、count 是数字、返回值是数字,形状完全正确;错的是意思。同类还有一大串:会员才打折但忘了判断会员、时区算错导致「今天」变成了昨天、两个人同时改同一笔订单……这些类型都不管,只能靠测试、日志,或你自己点一遍。
「上了 TypeScript 就不会崩了。」——它连上面那个 -95 都拦不住,更拦不住逻辑、数据、并发。
「它把形状类的错误从『上线后才发现』提前到『写的时候』。」——省的是排查时间,不是免掉测试。
自己动手:两条路,挑一条
路 A:不装任何东西(就是 §00 那个动作)
如果你已经做过 §00 的 60 秒动作,这条路就算走完了——那三个动作里已经包含了「第二类」问题的那条红线。想再看一遍,就回到 Playground,在最后一行把 finalPrice(order.price, order.count) 改成 finalPrice("35", 3)。
路 B:在自己电脑上跑(需要一个装好 Node.js 的目录)
mkdir ts-demo && cd ts-demo # 建一个空目录,并进入这个目录
npm init -y # 建一个空项目,生成 package.json(这个项目的清单文件)
npm install -D typescript # 把 TypeScript 装成「只在开发时用」的依赖
npx tsc --version # npx=直接运行某个工具(不用先全局安装);这会打印版本号
然后建两个文件。第一个是 src/order.ts,内容就是上面那份 order.ts(注意最后一行仍然写着拼错的 prcie)。第二个是项目根目录的 tsconfig.json——它是这个项目的「检查清单设置」,先照抄这份:
{
"compilerOptions": {
"target": "esnext", // 按最新的语言标准来检查
"module": "nodenext", // 按 Node 的模块规则来找文件
"moduleResolution": "nodenext",
"strict": true, // 打开整套严格检查(下面有逐项解释)
"noEmit": true, // 只检查,不产出 .js 文件
"skipLibCheck": true // 不检查第三方说明书文件内部,省时间
},
"include": ["src"] // 要检查 src 这个目录
}
现在跑检查:
npx tsc --noEmit
屏幕上应该出现上面那条关于 prcie 的报错。把拼写改对,再跑一次,什么都不会输出——「没有输出」就是「通过」,这是命令行工具的通用约定。
想让它真的跑起来,有两条路:
npx tsx src/order.ts # 用第三方工具跑(首次运行会临时下载它;它会先处理掉类型)
node src/order.ts # 用 Node.js 自带的「剥离类型」跑(需 Node 22.18+ 或 23.6+,且不做检查)
tsx 是社区工具,帮你把 .ts 处理成可执行的 JavaScript;node 直接跑 .ts 是 Node.js 官方后来加的能力(把类型替换成空白,不做任何检查,也忽略 tsconfig.json)。所以「能跑」和「检查过」在这里彻底分家了——见 §09。
留给你的那一问(这篇唯一需要你自己验证的判据)
回想你手上那个半成品项目,找出一处「改了一边、很可能忘了另一边」的地方——可能是某个字段名同时出现在好几个页面、可能是表格的某一列、也可能是两部分代码对接处(接口,也就是约定的对接方式)返回的数据里少了一个字段。
写下它,然后回答两个问题:① 这里的改动,类型检查能不能在那一步拦住我?② 如果不能,我原来打算靠什么发现它?
这篇其余部分我都能替你核,只有这一条不能——因为只有你知道你的项目长什么样。
04最近的七个邻居:长得像,但不是一回事
TypeScript 周围有一圈名字长得像、经常被混着说的东西。混起来会导致很实际的错误判断,比如把「装了检查代码风格的工具」当成「检查过类型」。
| 名字 | 它是什么(日常语言) | 和 TypeScript 的分工 / 什么时候你会听到它 |
|---|---|---|
| JavaScript(缩写 JS) | 浏览器和 Node.js 真正执行的语言 | TypeScript 就是「JS + 说明书」;把说明书擦掉,剩下的就是它。它不是 TypeScript 的替代品,是它的底子。 |
| JSDoc | 用注释写类型,不换文件。写法类似 /** @param {number} price */ | 不改成 .ts 也能被 TypeScript 检查。网站开发框架 Svelte 就选它(§06)。听到「我们用 JSDoc,不用 TS」时,指的是这条路。 |
| Flow | Facebook 当年做的另一个类型检查器,同样是「JS + 类型」 | React(Facebook 的界面库)早期用它,后来生态逐步转向 TypeScript。它证明「给 JS 加类型」不止一种做法——所以别把 TypeScript 当成唯一选择。 |
| ESLint | 检查「写法可疑 / 风格不合规」的规则工具,比如「定义了变量却没用」 | 它默认不懂类型(配了额外插件才懂)。所以「ESLint 全过」不等于「类型全对」。 |
| Prettier | 只管排版:缩进、换行、引号用什么 | 和正确性完全无关。它把代码排整齐,不代表代码是对的。 |
.d.ts 声明文件 | 只有类型、没有实现的「说明书文件」 | 用来描述那些没写类型的现成 JS 库。社区把这些说明书集中放在一个叫 DefinitelyTyped 的仓库里(5.1 万星),发布成 @types/xxx 包。 |
| 转译器(esbuild / swc / Babel) | 快,只负责把类型和较新的语法变成能跑的旧语法 | 不检查类型。大型项目通常两者都要:转译器负责快,tsc --noEmit 负责查。 |
和 JavaScript 的关系:同一台发动机,多一道出厂检查
这是全篇最需要说准的一对关系。先说对方的道理——JavaScript 的优点是真的:
- 写完就能跑,没有「先编译一步」的等待;
- 不需要额外配置文件;
- 在浏览器控制台里随手试一行,立刻看到结果。
TypeScript 的全部代价,正好是这些优点的反面(多一步、多一份配置、最初写起来更慢)。所以选择从来不是「谁更先进」,而是「你更怕哪一类成本」:怕改动时出错,还是怕起步慢。§06 会把两边的账摊开算。
一个常见疑问:到处都要写类型,不啰嗦吗
大部分位置不用手写,因为检查器会推断:
const price = 35; // 你没写,它推出:数字
const name = "小明"; // 你没写,它推出:文字
所以真正的判断标准不是「你写了多少类型」,而是你的代码里有多少「形状对不上」的地方——那才是报错的来源。
和 JSDoc 的关系:不换文件也能有类型
如果团队不想引入 .ts 文件和额外的构建步骤,还有一条路:保留 .js 文件,在注释里写类型,让 TypeScript 去读注释。这条路叫 JSDoc。
「我们用 JSDoc,所以没有类型检查。」——大部分情况下恰恰相反:主流的做法正是用 TypeScript 去检查 JSDoc 注释,只是文件本身还是 .js。
「我们不做类型检查」和「我们用注释写类型、不换文件扩展名」是两件完全不同的事,看配置里的 checkJs 开关就能分辨(§08)。
05为什么大项目都在用它:它挡住的不是手滑,是改动的连锁反应
先说结论:TypeScript 的核心价值不在「写第一版」,而在「改第 500 版」。一个项目的前 5% 时间里,它的价值几乎为零——那时你还在试想法,改起来飞快,类型只是负担。它的价值是在项目活过一段时间之后才出现的。
为什么会这样?因为软件里有一类事故,专门在「改动」时发生。
四类事故:没有类型时,你什么时候才会发现
| 事故类型 | 具体长什么样 | 没有类型时,你什么时候才发现 |
|---|---|---|
| 改了一处、忘了另一处 | 同一个字段名散落在十几个文件里,改名时漏掉了三个 | 用户恰好点到那个功能时;或者几周后报表数字对不上 |
| 两边对同一份数据的假设不一致 | A 模块(项目里一块相对独立的代码)给出的订单有 4 个字段,B 模块以为第 4 个叫 total,实际叫 amount | 联调(几部分拼在一起对接调试)时、上线后,取决于那条分支有没有被测到 |
| 新加入的人(包括 AI)不合原有约定 | 新代码传了文字进来,老代码按数字处理,现在恰好能跑 | 取决于运气——可能一直没事,直到某个输入变成另一种形状 |
| 依赖的库升级后悄悄改了形状 | 你用的库把某个函数算完交出来的结果(返回值)改了,你的代码没跟着改 | 升级之后跑起来才发现;有些要等线上(正式对外服务的环境)报错 |
这四类事故有一个共同点:它们在「写」的时候很难发现,在「跑」的时候才暴露,而那时的成本高得多。类型检查做的事,就是把它们从「跑的时候」搬到「写的时候」——成本相差几个数量级。
证据:它真的成了默认,不只是「很流行」
语言榜和数据
- 2025 年 8 月起,TypeScript 成为 GitHub 上使用最多的语言,第一次超过 Python 和 JavaScript;过去一年它新增了超过 100 万名贡献者,是所有语言里绝对增长最多的。同期 Python 增长约 85 万人(同比 +48.78%),JavaScript 约 42.7 万人(同比 +24.79%)。(GitHub 官方 Octoverse 数据分析,2026-02-03 发布。Octoverse 是 GitHub 每年发布的平台数据报告。)
- 下载量:
typescript这个包最近一周被下载 209,215,221 次,最近 30 天 1,004,355,023 次。(本文 2026-09-23 从 npm 官方接口自取) - 它的创造者原本的预期是「25% 就算成功」。Anders Hejlsberg 在 2025 年的访谈里说:「我当年想,如果能让 JavaScript 社区里 25% 的人感兴趣,那就是成功。现在这样?我完全没想到。」(GitHub Blog 访谈,2025-11-06)
几个知名项目的实际构成(本文自算,方法见 §12)
「大项目都在用」这句话值得拆开看,因为事实比口号复杂。以下比例来自 GitHub 官方的语言统计接口,按源码字节数计算,2026-09-23 当天取:
| 项目 | TypeScript 占比 | 这个数字说明什么 / 别拿它推出什么 |
|---|---|---|
| VS Code(微软的代码编辑器,全世界很多开发者在用) | 96.1%(JavaScript 只占 0.57%) | 这是「整项目押注 TypeScript」的极端样本。别推出「所有编辑器都这样」。 |
| Angular(Google 的前端框架) | 87.3% | 框架从一开始就把 TypeScript 当默认语言。别推出「框架都必须用它」——见下面两行。 |
| Next.js(流行的网站开发框架) | 33.7%(JavaScript 50.2%) | TypeScript 是新项目的默认选项,但仓库里仍留着大量历史 JavaScript。 |
| React(Facebook 的界面库) | 22.4%(JavaScript 49.5%) | React 自己当年选了另一个类型方案(Flow),历史包袱留到现在。 |
| Svelte | 4.9%(JavaScript 70.9%) | 它是主动选择不用 TypeScript 的,理由见 §06。这一行是本文用来防止你过度概括的。 |
| TypeScript 自己 | 12%(Go 87.5%) | 7.0 把编译器从 TypeScript 重写成了 Go。连它自己都不是「纯 TS 项目」了(§09)。 |
它到底省下多少:三个数字,三种口径
- 15%——一篇同行评审研究(ICSE 2017,Gao、Bird、Barr)的做法是:找出 JavaScript 项目里已经修掉的真实 bug,回到出错前的那一刻,加上类型注解,看类型检查器能不能提前报错。结论是 Flow 和 TypeScript 2.0 都能在 15% 的 bug 上报错。研究本身强调这是偏保守的数字:这些 bug 已经通过了测试和代码评审,属于「最难的那批」。
- 38%——业界流传着「Airbnb 有 38% 的 bug 本可以被类型挡住」的说法。本文把它标为二手信息:能找到的只有技术社区的讨论串转述,没有找到可核查的一手报告,所以它只用来告诉你「业界确实认真算过这笔账」,不当成事实引用。
- 7.5 分钟 → 1.25 分钟——这是成本侧的数字:Slack 团队的类型检查耗时(TS 6 → TS 7)。它提醒你,这类「检查」的真正代价是时间和耐心。
15% 来自一次受控的学术实验;38% 来自无法追溯的一手来源;而 Eric Elliott 那篇广为流传的批评文章(§06)甚至认为整体上不划算。三者的方法、样本、口径都不同,不能当成同一件事的三个估计值。本文的处理是:都保留、都标出处、说清各自能支撑什么结论——15% 能支撑「它能提前发现一部分真实 bug」;38% 只能支撑「有人算过」;「不划算」只能支撑「成本是真实存在的」。
为什么关键词是「大」和「多人」
如果你只记一句原因,记这句:类型检查是一份「改动保险」,而保险的价值等于「改动次数 × 每次改错的代价」。
- 人多:你要用别人写的东西,别人要用你写的。双方不可能都记得对方每个函数的约定——类型就是那份不会说谎、也不会忘记的约定。它同时也是给新人看的文档:不用问人,看一行声明就知道该传什么。
- 活得久:今天的你给三个月后的自己留陷阱,是最常见的事故来源。类型检查在这里相当于一个「记得所有约定」的同事,而且他每次都会检查。
- 有别人(或 AI)在改你的代码:约定越明确,外人改错的概率越低。这一条在 2026 年变得比过去更重要,单独放在 §07 讲。
反过来,如果项目小、人少、改几次就扔——这个乘积很小,保险费就显得贵。这不是「他们落后」,是成本结构不同(§06 会给出真实的反例)。
06代价:为什么不是所有项目都该上
只讲好处的介绍不值得信。这一节把账摊开,包括真实发生过的「换回去」。理解这些成本之后,你才能判断某个具体项目该不该上。
五笔实际成本
| 成本 | 具体是什么 | 什么情况下这笔成本会失控 |
|---|---|---|
| 检查要花时间 | 每次改动多一道检查。小项目几秒,大项目在旧版本上曾以分钟计(Slack 曾达 7.5 分钟) | 检查没接进「合并前必须通过」的流程时:要么没人跑,要么靠某个人手动记得跑 |
| 多一份配置要维护 | 项目里多出一个 tsconfig.json,里面几十个开关 | 开关被随手改松(比如把 strict 关掉)时,它就从「保障」退化成「装饰」 |
| 老代码迁移 | 把已有的 JavaScript 项目改造成 TypeScript,往往是一次大工程;打开严格检查后,老代码可能一次报出成千上万条错 | 没有分阶段计划、想一天之内全改完 |
| 学习曲线与「绕过去」的诱惑 | 类型写法本身有难度(尤其泛型这类高级用法——泛型就是让一个类型带参数,好让「一列数字」和「一列文字」共用同一套写法),于是出现一批逃逸口:any(这里什么类型都行)、as(按我说的类型算,别再问)、@ts-ignore(跳过这一行的报错) | 团队把「让报错消失」当成目标,而不是「让代码正确」——此时 TypeScript 变成纯粹的负担(§07 的清单正是查这个) |
| 工具链错位 | 生态工具要跟着编译器一起升级,而升级不同步 | 正在发生的例子:TypeScript 7.0 还没提供稳定的程序接口,导致一部分周边工具(含部分框架与 ESLint 相关工具)暂时还得用旧编译器 |
有人真的换回去了:Svelte 的两次选择
这是全篇最有价值的一个反例,因为它说明这件事是权衡,不是信仰。
- 2023 年,Svelte 把编译器内部代码从 TypeScript 换回 JavaScript + JSDoc。项目负责人 Rich Harris 给出的理由包括:工具链带来的摩擦、需要同时维护两套编译过程、以及迭代速度受影响。他的原话里有个说法值得记住:这样做「消除了一整类恼人的小毛病」。
- 2025 年 8 月,有人开 issue 提议改回 TypeScript(issue 就是项目里公开记问题、提建议的讨论区),理由是环境变了:Node.js 从 23.6 起能原生跑
.ts,过去的编译等待不再是问题;同时社区里「本来就写 TypeScript」的贡献者变多了,用 JS + JSDoc 反而抬高了参与门槛。
同一个项目,两年之内出现了方向相反的两个主张——这不是打脸,这是成本结构变了。2023 年真正压垮天平的是工具链摩擦;到 2025 年,这套摩擦因为「运行时能直接跑 .ts」而变小了;2026 年编译器换成 Go 之后,当年那些抱怨的对象又少了一个。你在自己项目里要判断的,正是同一个天平。
一个被广泛引用的反对意见
2019 年,开发者 Eric Elliott 写了一篇长文《The TypeScript Tax》,系统性地列出使用 TypeScript 的成本,结论是:他不愿再为大项目选择 TypeScript。这篇文章至今仍是被引用最多的批评之一。
本文对它的处理是:保留论点,同时标注它的局限。它是单个团队的经验与推算,不是受控实验;它的很多具体数字(比如「多写多少代码」「多花多少时间」)来自该团队的估算,后来被多方质疑过口径。所以它能支撑的结论是「成本真实存在、值得算」,不能支撑「TypeScript 总体上不划算」。
结论:它买的是一份「改动保险」
问四个问题:① 这个项目要改多少次?② 有多少人(含 AI)会碰它?③ 它打算活几年?④ 改错的代价是什么(内部工具 / 用户能看到 / 涉及钱)?
前三个答案越大、第四个代价越高,这份保险就越值。反过来——几百行的一次性脚本、只用来验证想法的原型、改一次就扔的临时页面——加类型不是「更专业」,是白白多一道手续。在这些场景里,选 JavaScript 是完全正当的工程判断,Svelte 2023 年的选择就是活证据。
这篇写给谁看
适合:要判断别人或 AI 关于 TypeScript 的建议、但自己不动手写代码的人(产品、运营、设计师、管理者、以及被 AI 带着做项目的个人开发者)。
不太适合:准备正式动手写 TypeScript 的人——这篇能给你判断框架和关键概念,但装环境、配工具链、写类型的细节,应该直接看官方手册,那里比任何二手教程都准。
07AI 时代:它为什么突然更值钱,以及你怎么看 AI 的 TS 建议
一个观察:AI 写代码的失败,很多是「形状对不上」
GitHub 在分析自家平台数据时给出的解释是:实践中团队遇到的 AI 生成代码的失败,很大一部分表现为类型不匹配、接口被破坏、组件之间对彼此的假设不一致——而更强的类型系统可以在这些错误到达生产环境(真正给用户用的那一套)之前把它们拦下来。他们的结论写得很直接:「如果你打算在软件开发里用 AI,强类型语言是你的朋友。」(GitHub Blog Octoverse 分析,2026-02-03)
为什么会这样不难理解。AI 写代码时最容易出的问题,恰好是这几种:它记住了某个名字,但记错了形状;它写了一个函数,但调用它的时候参数顺序不对;它假设某个对象有一个字段,那个字段其实叫另一个名字。这些正好都是类型检查最擅长的事。
为什么类型和 AI 特别合得来
- 检查是机械的,答案只有「过」或「不过」。它不需要有人判断「这个 AI 说得有没有道理」,只需要跑一条命令。
- AI 也擅长写它。Hejlsberg 的说法是:「AI 写某种语言的能力,与它见过的这种语言的代码量成正比……新语言反而吃亏。」TypeScript 的语料量(训练 AI 时喂给它的文本总量)正好极大。
- 它给了你一个不需要读懂代码的验收信号。这一点和本站《CI/CD 怎么用》里那条核心判断是同一件事:AI 可以声称「我改好了」,但没法把红的说成绿的——前提是你真的让检查跑起来,而且看得懂那个结果。
七个危险信号:AI 的 TypeScript 改动里,看到这些就该追问
下面这些写法在 TypeScript 里都是合法的——它们不是错误,是「把检查关掉」的开关。单独出现一两处很正常;成片出现,说明这个项目的类型纪律有问题。
| 你看到的东西 | 它实际在做的事 | 你该追问什么 |
|---|---|---|
any | 把类型检查在那一处彻底关掉:这个值是什么都行,别查我 | 「这里为什么写不出具体类型?」——往往答案就是「写不出来,所以放弃了」 |
as(类型断言) | 你告诉检查器「我说它是这个类型,你别问」。注意:这不会在运行时做任何转换 | 「这是真的检查,还是让编译器按我说的算?」两者差别巨大 |
@ts-ignore / @ts-expect-error | 直接跳过下一行的报错 | 「跳过的是哪一行?为什么这一行是错的却不管?」 |
@ts-nocheck | 整个文件不检查 | 「这个文件多大?打算什么时候拆?」 |
"strict": false,或者把 strict 整行删掉 | 把一整套严格检查关掉(§08 有逐项解释) | 「原来是不是开着的?谁关的、为什么关?」——这是一条很硬的信号 |
只把 .js 改名成 .ts,其他没动 | 语法上完全合法,可能一条错都没有;但也可能一行都没检查过 | 「改名之后跑过检查吗?把输出贴出来。」 |
| 「改成 TypeScript 就不会崩了」 | 把类型当成了逻辑正确性的保证 | 「这次改动里,类型挡不住的部分(§03 第三类)打算怎么验?」 |
一条可复制的验收要求
不需要你读懂代码,你只需要把这句话发给 AI 或同事:
「请在项目根目录跑 npx tsc --noEmit,把完整输出贴给我;如果没有任何输出,请明确说一句『没有输出,检查通过』,并告诉我是哪个配置文件在生效。」
这句话为什么管用:① --noEmit 只检查、不改任何文件,跑它没有风险;② 有输出就必须贴出来,含糊不过去;③ 最后半句逼它指出生效的配置文件——如果有人把 strict 关掉了,这一步常常会露出来。
如果这个项目的构建工具是 esbuild / swc / Babel,或者它用 Node.js 直接跑 .ts,那么「代码能跑」完全不能说明类型检查跑过(§02 那张表)。这也是 AI 最容易含混过去的地方:它能跑通一个演示,就说「搞定了」。
08不写代码,怎么读一份 tsconfig.json
tsconfig.json 是这个项目的「检查清单设置」文件,就是一个普通的文本文件,放在项目根目录。它不需要你懂编程也能读——里面全是开关。
你只需要认识七个。先说两个会反复出现的取值:配置里的 true 表示「开」,false 表示「关」。
| 开关 | 它管什么(日常语言) | 该是什么样 / 别当什么用 |
|---|---|---|
strict | 总开关:一次打开整套严格检查。官方说新代码库应该总是开着它 | 应该是 true。它是 false,后面那些严格项基本就形同虚设 |
noImplicitAny | 「不许偷懒」开关:检查器推不出类型时,默认放行成 any(等于不查);打开它就不许这么放行 | 官方点名的两个最重要开关之一,由 strict 一并打开 |
strictNullChecks | 「空值」开关:把「可能没有值」这件事标记出来,逼你处理 | 另一个最重要的开关,同样由 strict 打开。它挡的正是 §03 里那类「取到 undefined」的事故 |
noEmit | 「只检查、不产出文件」 | 检查脚本里常见。别把它理解成「不检查」——正好相反 |
skipLibCheck | 跳过第三方说明书文件内部的检查,省时间 | 常见且合理。但它只跳过别人写的说明书,不跳过你的代码 |
allowJs / checkJs | 要不要连 .js 文件一起纳入;要不要连 JSDoc 注释里的类型也一起检查 | 两个都开,就是「不换文件也有类型检查」那条路线(§04) |
include / exclude | 检查哪些目录、跳过哪些目录 | 最容易被做手脚的地方:把出问题的目录写进 exclude,报错就消失了——代码一行没改 |
一份可以照抄的起步配置
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src"]
}
如果这个项目打算用 Node.js 直接跑 .ts(不做构建),Node 官方文档给出的推荐组合是这样的——比上面那份多了后三项,它们的作用都是「让代码只使用能被安全擦除的语法」:
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true, // 导入语句里可以写 .ts,输出时自动改写成对应名字
"erasableSyntaxOnly": true, // 禁止「擦不掉」的语法(见下)
"verbatimModuleSyntax": true // 导入导出的写法保持原样,不做隐式删改
}
}
最后那个 erasableSyntaxOnly 值得多看一眼:它专门用来禁止那些「擦不掉」的 TypeScript 语法——比如 enum、namespace 这类会留下运行时代码的写法。为什么重要:Node 只是把类型替换成空白,如果文件里出现了必须被「翻译」的语法,Node 会直接拒绝运行;这个开关让你在写的时候就知道,而不是等到部署时才发现。
两处「纪律证据」
看完配置文件,再看两个地方,你对一个项目的类型纪律就有判断了:
- 1有没有人把检查接进了自动流程(CI)
去看仓库里那个「每次提交都自动跑一遍」的配置文件,里面有没有一步在跑类型检查。本地检查靠自觉,机器检查才有否决权。
- 2有没有人在用逃逸口还债
在仓库里搜三个词:
any、@ts-ignore、as。数量本身不是罪证,但「strict开着 + 满屏@ts-ignore」通常意味着这套检查只剩形式。
CI 那一步的写法长这样(放进仓库的 .github/workflows/ 目录,是一份 YAML 文本——一种靠缩进表示层级的配置格式,缩进错了就不生效):
name: 类型检查
on:
push: # 有推送时
pull_request: # 有人提出合并请求时
jobs:
typecheck:
runs-on: ubuntu-latest # 在平台临时给的 Linux 机器上跑
steps:
- uses: actions/checkout@v4 # 版本号会过期:以该仓库 Releases 页(GitHub 上记录各版本发布的地方)的最新版为准
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci # 按项目锁定记录精确安装依赖
- run: npx tsc --noEmit # 这一行才是「真的在检查」
@v4 这类版本号会过期(本站《CI/CD 怎么用》在 2026-09-23 核过一次:官方入门模板写着旧版本,而实际最新版已经更新了好几代)。本文给出的是结构:引用官方动作 → 装 Node.js → 装依赖 → 跑类型检查。版本号请去对应的 Releases 页面抄当期值。
092026 年的版本口径:你现在听到的三种说法都没错,但说的不是一回事
关于 TypeScript,2026 年最容易听到三句话。它们各自成立,混起来就会得出错误结论。
| 你听到的说法 | 它具体指什么 | 你不该由它推出什么 |
|---|---|---|
| 「Node 现在能直接跑 TypeScript 了」 | Node.js 从 22.6 起加入「剥离类型」能力,23.6 / 22.18 起默认开启,25.2 / 24.12 起稳定。它把类型语法替换成空白再执行,所以不需要额外的构建步骤 | 不该推出「Node 会检查类型」。官方文档原话是「不做类型检查」,而且「会忽略 tsconfig.json」 |
| 「TypeScript 7 快 10 倍」 | 2026-07-08 发布的 7.0 把编译器从 TypeScript 重写成了 Go。官方基准里 VS Code 全量构建从 125.7 秒降到 10.6 秒(11.9 倍),内存下降约 18%;Slack 的 CI 类型检查从约 7.5 分钟降到约 1.25 分钟 | 不该推出「你的项目也会快 10 倍」(项目越小、加速越不明显),也不该推出「生态已经跟上」 |
| 「TypeScript 6 是最后一个用 TypeScript 写的版本」 | 6.0(2026 年 3 月)是一个过渡版本:引入了一批弃用项,为 7.0 铺路。官方给了缓冲开关 ignoreDeprecations: "6.0",但 7.0 不再支持这些被弃用的选项 | 不该推出「6.0 已经不能用」;也不该推出「升级到 7 一定零改动」 |
为什么编译器要用 Go 重写
TypeScript 原先是用它自己写的(这叫「自举」)。Hejlsberg 在访谈里的说法是:「尽管让我们放弃自举很痛苦,但我们知道没法再从它身上榨出更多性能了。」他们还试过 C#,最后选了 Go,理由是性能提升 10 倍——「一半来自变成原生程序,一半来自共享内存的并发。」(并发=同时用多个处理器核心干活。)
他还强调了一件事,对使用者更重要:「我们的原生编译器是旧编译器的一比一复刻,连怪癖都一样。社区不需要扔掉任何东西。」这句话解释了为什么升级到 7 大体上是平滑的。
但平滑不等于没有摩擦。7.0 还没有提供稳定的程序接口(官方说计划在 7.1 提供),所以那些需要「读取编译器内部信息」的工具——包括一部分框架工具和 ESLint 相关工具——暂时还得继续用基于旧编译器的版本。快,和可用,是两条不同的进度线。
「Node 能直接跑 .ts」这条最容易误解
先看官方文档原文的三句关键信息(Node.js v26.10.0 文档,Modules: TypeScript):
- 「Node.js will replace TypeScript syntax with whitespace, and no type checking is performed.」——Node.js 会把 TypeScript 语法替换成空白,不做任何类型检查。
- 「Node.js ignores tsconfig.json files.」——它会忽略 tsconfig.json,所以依赖这份配置的功能(比如路径别名、把新语法转成旧语法)都不支持。
- 想要完整的 TypeScript 支持(含类型检查),官方建议用第三方工具,比如
tsx。
所以正确的心智模型是:Node.js 让「跑起来」变简单了,但「检查」一步没变,还是得自己跑 tsc --noEmit。
三条对你有用的结论
- 「能跑」不是证据。现在有三种方式能让
.ts代码跑起来(构建一次 / Node 剥离类型 / 第三方工具),其中只有一种会检查。 - 版本号要跟着年份读。你看到的任何一篇 TypeScript 文章,先看它写的版本和日期;「TypeScript 很慢」在 2026 年已经主要是历史问题了(但生态工具还没全部跟上)。
- 别因为「有新版本」就急着升级。7.0 的性能收益很大,但它缺稳定接口,意味着你的工具链可能要等一等。升级的判断依据应该是「我的工具都支持了吗」,不是「新版本号是多少」。
10一页纸:五问 + 命令速查
五个问题:拿到任何一句「我们上 TypeScript 了」,按顺序问
npx tsc --noEmit。「没有输出」=通过。贴不出输出,就等于没检查。strict 是开着的吗?tsconfig.json。它是 false 或被删掉,后面的严格检查基本不作数。any / @ts-ignore?命令速查:想自己核一遍的时候,敲这几条
| 你想知道什么 | 命令 / 操作 | 说明 |
|---|---|---|
| 亲手看一次类型检查的报错 | 打开 typescriptlang.org/play,改一行代码 | 不需要装任何东西,浏览器里就能看到红波浪线与报错文字 |
| 在项目里装 TypeScript | npm install -D typescript | -D 表示「只在开发时用的依赖」,不会进入发布产物 |
| 只检查、不改任何文件 | npx tsc --noEmit | 最安全的一条。有输出=有错,无输出=通过 |
| 看当前装的是哪个版本 | npx tsc --version | 对照 §09 的版本口径,能判断一篇文章有没有过时 |
直接跑一个 .ts 文件(不检查) | node src/order.ts | 需 Node 22.18+ / 23.6+。没有任何类型检查 |
| 自己核一遍本文的数字 | curl -s https://api.npmjs.org/downloads/point/last-week/typescript | 会返回一段 JSON(一种纯文本的数据格式),里面的 downloads 就是当周下载量(§12 还给了语言构成的复核命令) |
11名词速查:别把它们当成什么用
| 术语 | 定义 | 为什么重要 / 何时用 · 别当什么用 |
|---|---|---|
| 类型(type) | 一个位置该放什么种类的值:数字、文字、真假、一组固定字段的对象 | 它是 TypeScript 唯一的概念基础。别把它当成「值的检查」——它检查的是形状,不是内容对不对 |
| 类型注解(annotation) | 你手写的那句声明,如 price: number | 关键接口处手写,其余靠推断。别以为「注解越多越好」,重复推断能得出的注解只是噪音 |
| 类型推断(inference) | 你没写,检查器自己从上下文猜出类型 | 它解释了「为什么改名成 .ts 常常不报错」。别把推断当成保证:它猜的能力有限,边界处必须手写 |
| 类型检查(type checking) | 拿声明去核对每一处使用,对不上就报错 | 真正的保障来源。别和 ESLint 混为一谈(那查的是写法风格) |
| 类型擦除(erasure) | 检查完成后把类型删掉,产出不含类型信息的普通 JavaScript | 理解边界的钥匙:它意味着运行时没有保护。别指望它能在用户输入时拦截 |
| 超集(superset) | 「JavaScript 里合法的写法,在 TypeScript 里全都合法」 | 用来打消「要重学一门语言」的顾虑。别理解成「TypeScript 是另一门语言」 |
| 编译 / 构建 | 把 .ts 处理成可执行的 JavaScript 这一步(也可能只做检查) | 大项目的构建里通常还夹着打包、压缩等步骤。别把「构建成功」当成「类型检查过了」——它们常常是两步 |
| 运行时(runtime) | 程序真正执行的那个环境:浏览器或 Node.js | 判断一切「保护」都要先问「它在不在运行时生效」。类型不在 |
tsc | TypeScript 自带的编译器命令,做检查和擦除两件事 | 「真的检查过」的唯一来源。别以为装了 TypeScript 就等于它在跑 |
tsconfig.json | 项目的检查清单设置文件 | 纪律的温度计(§08)。别只看它存在——要看 strict 与 include |
.d.ts 声明文件 | 只有类型、没有实现的说明书文件,用来描述现成的 JS 库 | 生态能跑起来靠它。别把它当实现文件——里面没有可执行的逻辑 |
@types/xxx | 社区补写的类型说明书包(集中放在 DefinitelyTyped 仓库) | 装库时常常顺手装上。它的质量参差,说明书本身也可能写错 |
any | 「什么类型都行」——等于在这一处放弃检查 | 一处两处正常,成片出现说明这套类型只是装饰 |
strict | 一次打开整套严格检查的总开关 | 官方建议新代码库总是打开。关掉它不是「配置不同」,是「没在检查」 |
类型断言(as) | 告诉检查器「按我说的类型算」,不再追问 | 偶尔用于你确实比编译器更懂的地方。别当成转换器:运行时不发生任何转换 |
@ts-ignore | 注释形式的开关,跳过下一行的报错 | 每出现一次都值得问一句原因 |
| 剥离类型(strip types) | Node.js 的能力:把类型语法替换成空白后执行 | 让「跑 .ts」不再需要构建一步。别当成类型检查(§09) |
| JSDoc | 用注释写类型的做法,文件仍是 .js | 不换文件也有类型的路线(Svelte 走的就是它)。别当成「没有类型」 |
| CI | 每次提交后自动跑一串检查的服务器流程 | 让类型检查具备「否决权」的地方。不接进 CI 的检查靠自觉,等于没有 |
12来源与证据边界
官方与一手来源
- TypeScript 官方手册 — TypeScript for the New Programmer:超集的定义、类型擦除、运行时不改变行为、以及「JavaScript 的运行环境 + 编译时类型检查器」这句话都出自这里(本文 2026-09-23 抓取)
- TypeScript 6.0 发行说明:过渡版本、弃用项、
ignoreDeprecations、以及--stableTypeOrdering(一个用来对比 6 与 7 输出差异的临时开关,官方说可能拖慢检查最多 25%) - tsconfig 文档 — strict 与 手册 — Everyday Types:严格检查各开关、
noImplicitAny与strictNullChecks的官方措辞 - Node.js 官方文档 — Modules: TypeScript:剥离类型的版本时间线、「不做类型检查」、「忽略 tsconfig.json」、以及推荐给第三方工具的用法
- esbuild 官方文档 — Content Types:解析并丢弃类型注解,但不做类型检查,建议并行跑
tsc --noEmit - microsoft/TypeScript Releases:7.0.2 的发布记录(GitHub 上的镜像 tag 发布于 2026-08-20;7.0 于 2026-07-08 首发)
- sveltejs/svelte issue #16647(2025-08-19):提议把编译器改回 TypeScript,并转述了 2023 年改用 JS + JSDoc 的理由
二手与有争议的来源
- GitHub Blog — Octoverse:增长最快的工具说明了什么(2026-02-03,2 月 12 日更新):2025 年 8 月 TypeScript 成为 GitHub 上使用最多的语言;贡献者增长数字;关于 AI 生成代码失败形态的判断
- GitHub Blog — 对 Anders Hejlsberg 的访谈(2025-11-06):「25% 就算成功」、Go 重写的 10 倍、一比一复刻式兼容、AI 与新语言
- InfoQ — TypeScript 7.0 发布报道(2026-08-03):8–12 倍加速、VS Code / Slack 的耗时数据、7.0 没有稳定程序接口
- Wikipedia — TypeScript:2012-10-01 首发、设计者、7.0 的时间线与 Go 重写,用来交叉核对上述媒体报道
- The TypeScript Tax(Eric Elliott,2019):反对意见的代表作,成本估算口径被多方质疑
- 「Airbnb 38% 的 bug 本可被类型挡住」:只追溯到技术社区的讨论串转述,未找到一手报告,本文标注为二手
本文自己算的数据,以及你怎么复核
下面三组数字不是从文章里抄的,是本文在 2026-09-23 当天直接查官方接口得到的。任何人都能重做一遍:
# 1) TypeScript 的下载量(返回 JSON,看 downloads 字段)
curl -s https://api.npmjs.org/downloads/point/last-week/typescript
curl -s https://api.npmjs.org/downloads/point/last-week/@types/node
# 2) 某个仓库的语言构成(按字节数返回)
curl -s https://api.github.com/repos/microsoft/vscode/languages
curl -s https://api.github.com/repos/sveltejs/svelte/languages
# 3) TypeScript 的发布记录(tag 与时间)
curl -s "https://api.github.com/repos/microsoft/TypeScript/releases?per_page=6"
证据边界与时效说明
① 时效:这篇属于「演进型」——机制部分(擦除、检查、能跑≠检查过)不容易过时,但版本号、下载量、语言占比、各家工具的进度都在变。本文校对日期为 2026-09-23,所有数字都是当日取的值。
② 「快 10 倍」的口径:8–12 倍来自微软自己发布的基准(VS Code、Sentry、Bluesky、Playwright 等项目),经媒体报道转述,不是独立第三方的对照测量。项目越小,加速通常越不明显。
③ 15% 那个研究的保守性:它测的是「已经公开的 bug」(也就是已经通过测试与代码评审之后仍然发布出去的那批)。研究自己写明,这个口径会低估类型系统在私下开发与评审阶段的作用。
④ 下载量不能当人数:npm 的下载次数包含了自动化流程、持续集成、镜像与重复安装,一次提交可能触发几十次下载。它只能说明量级与趋势,不能说明「有多少人在用」。
⑤ 语言占比是个粗指标:GitHub 的语言统计按源码字节数计算,受生成文件、第三方代码、压缩文件影响。本文只用它做横向对照,不用它下精确结论——比如「Next.js 有 33.7% 是 TypeScript」不意味着「它 33.7% 的功能用 TypeScript 写」。
99一句话总结
TypeScript 是 JavaScript 加了一层「只在检查时存在、运行前就被擦掉」的说明书;它买下的不是「更少的 bug」,而是「改动时更早知道你碰坏了什么」。
所以它值不值,取决于你的项目要改多少次、有多少人碰它、要活多久;大项目都在用它,是因为这三个数字都很大,不是因为潮流。而对不写代码、靠 AI 推进项目的你,它最实际的用处是一句话:「请在根目录跑 npx tsc --noEmit,把输出贴给我。」——它能跑、能过、能贴出结果,你不需要读懂代码就能验收;如果它绕开这一步谈「安全」,那你已经知道该看哪几个词了(any、@ts-ignore、strict: false、只改了扩展名)。