Typescript总结篇(二) 语法全景与工程配置
写完上一篇把 TypeScript 和 React 接起来之后,我发现一个问题:项目能跑,但很多规则我只是「照着写」,不知道为什么。比如为什么定义了任意属性之后,可选属性的类型也被限制住了;比如为什么函数重载要把精确的签名写在前面。这些细节在赶项目的时候不影响你交付,但一旦出错,你会盯着报错信息发呆很久。
所以这篇是一次系统性的补课。我按照官方 Handbook 的脉络,把 TypeScript 的语法从原始类型一路梳理到泛型和声明合并,每一条规则都配上会报错的反例和真实的报错信息。目标很明确,下次编译器拦你的时候,你能一眼认出这是哪条规则在起作用,而不是靠加 any 蒙混过去。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- TypeScript 是什么、它和 ES6/ES5 的关系,以及它的优点和真实存在的成本
- 五种原始数据类型的标注方式,
any的传染性,还有类型推论到底在什么时候生效 - 联合类型的访问规则,以及接口的四种属性形态(确定、可选、任意、只读)之间的约束关系
- 数组的四种定义方式、类数组为什么不能标成数组
- 函数声明与函数表达式的类型写法、可选参数、默认值、剩余参数和函数重载的匹配顺序
- 类型断言的两种语法、它能做什么和绝对不能做什么
declare声明语句、.d.ts声明文件、@types第三方声明文件和内置对象的类型来源- 类型别名、字符串字面量类型、元组的越界规则
- 枚举的手动赋值、常数项与计算所得项、常数枚举和外部枚举
- 类的三种访问修饰符、抽象类,以及类与接口的四种关系
- 泛型的约束、泛型接口、泛型类和泛型参数默认类型
- 函数、接口、类的声明合并规则
tsconfig.json全量编译选项注释,以及一组可以直接抄的实战代码示例
这篇写于 2018 年底,当时 TypeScript 是 3.2。语法主干这些年基本没动,但周边有几处变了(namespace 和三斜线指令让位给 ES module,enum 有了新的替代方案,satisfies 和模板字面量类型是后来才有的),我会在对应的小节里另起一段说明。第一篇 Typescript基础及结合React实践(一) 讲的是怎么把环境搭起来把项目跑通,这篇专攻语法本身,两篇分工不同,重复的部分我尽量做了区分。
# 一、简介
# 1.1 什么是 TypeScript
TypeScript是JavaScript的一个超集,主要提供了类型系统和对ES6的支持TypeScript是由微软开发的一款开源的编程语言TypeScript是Javascript的超级,遵循最新的ES6、Es5规范。TypeScript扩展了JavaScript的语法TypeScript更像后端java、C#这样的面向对象语言可以让js开发大型企业项目
# 1.2 为什么选择 TypeScript
Typescript和es6、es5关系
这三者的关系用一张同心圆图最清楚:ES5 是内圈,ES6 在它外面又加了一圈,TypeScript 是最外面那圈。也就是说合法的 ES5 代码天然是合法的 TypeScript 代码,反过来不成立。

这张图能解释一件事,为什么迁移成本比想象中低。.js 改名成 .ts 就能编译通过(前提是不开严格模式),你可以一个文件一个文件慢慢加类型,不用停下来做大重构。
TypeScript 增加了代码的可读性和可维护性
- 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
- 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
- 增强了编辑器和
IDE的功能,包括代码补全、接口提示、跳转到定义、重构等
TypeScript 非常包容
TypeScript是JavaScript的超集,.js文件可以直接重命名为.ts即可- 即使不显式的定义类型,也能够自动做出类型推论
- 可以定义从简单到复杂的几乎一切类型
- 即使
TypeScript编译报错,也可以生成JavaScript文件 - 兼容第三方库,即使第三方库不是用
TypeScript写的,也可以编写单独的类型文件供TypeScript读取
TypeScript 拥有活跃的社区
- 大部分第三方库都有提供给
TypeScript的类型定义文件 Google开发的Angular2就是使用TypeScript编写的TypeScript拥抱了ES6规范,也支持部分ESNext草案的规范- 最新的
Vue、React也可以集成TypeScript
TypeScript 的缺点
- 有一定的学习成本,需要理解接口(
Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念 - 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,
TypeScript能够减少其维护成本 - 集成到构建流程需要一些工作量
- 可能和一些库结合的不是很完美
这份优缺点清单里有两条我想再强调。「Angular2 是用 TypeScript 写的」在 2018 年还是个有力的背书,现在更有说服力的是 Vue 3、Vite、Next.js、Nest、Playwright 这些主流工具全都是 TypeScript 写的,@types 反而成了少数派。另一条「即使编译报错也会生成 JS 文件」,这个行为可以用 noEmitOnError 关掉,团队里建议关掉,不然 CI 会给你一种「构建成功」的错觉。
至于缺点里的学习成本,我自己的感受是它不是一次性的。刚上手两周就能写业务,但真正遇到复杂类型(高阶组件的 props 推断、库的泛型签名)时会再卡一次,这一次的坎比第一次高。
# 1.3 安装 TypeScript
typescript 安装
npm i typescript -g
全局安装完成后,我们新建一个
hello.ts的ts文件
// hello.ts内容
let a = "poet"
接下来我们在命令行输入
tsc hello.ts来编译这个ts文件,然后会在同级目录生成一个编译好了的hello.js文件
// hello.js内容
var a = "poet";
那么我们每次都要输
tsc hello.ts命令来编译,这样很麻烦,能否让它自动编译?答案是可以的,使用vscode来开发,需要配置一下vscode就可以。
首先我们在命令行执行
tsc --init来生成配置文件,然后我们在目录下看到生成了一个tsconfig.json文件
生成出来的配置文件是这样,里面大部分选项都是注释掉的,可以当成一份带说明的清单来读。

这个
json文件里有很多选项
target是选择编译到什么语法module则是模块类型outDir则是输出目录,可以指定这个参数到指定目录
更多细节 https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html
接下来我们需要开启监控了,在
vscode任务栏中
在任务栏里选 tsc: 监视,编译器就常驻后台,改一次保存编译一次。

开起来之后,终端会一直挂着输出。这一步做完你才有一个能反复试错的环境,下面几十个反例都得靠它去验证。
# 1.4 Hello TypeScript
将以下代码复制到
hello.ts中
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'poetries';
console.log(sayHello(user));
tsc hello.ts
//这时候会生成一个编译好的文件 hello.js:
function sayHello(person) {
return 'Hello, ' + person;
}
var user = 'poetries';
console.log(sayHello(user));
TypeScript中,使用:指定变量的类型,:的前后有没有空格都可以
TypeScript只会进行静态检查,如果发现有错误,编译的时候就会报错TypeScript编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件
对比一下 hello.ts 和 hello.js 就会发现,除了 let 变 var、: string 被删掉,什么都没变。这就是理解 TypeScript 的第一把钥匙,它是纯编译期的东西,不给运行时增加任何负担,也就意味着它保护不了运行时真正收到的数据。接口返回的数据跟你声明的类型不一致,编译器一无所知,该炸还是炸。想在运行时也校验,得另外上 zod 这类运行时校验库。
# 二、基础
这一章覆盖日常写业务九成会用到的东西。我建议不要跳着看,因为后面几节的规则是叠在前面的规则上的,比如「任意属性」那条约束你不先理解索引签名就会觉得莫名其妙。
# 2.1 原始数据类型
JavaScript的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
- 原始数据类型包括:
布尔值、数值、字符串、null、undefined以及ES6中的新类型Symbol。
本节主要介绍前五种原始数据类型在
TypeScript中的应用
# 2.1.1 布尔值
布尔值是最基础的数据类型,在
TypeScript中,使用boolean定义布尔值类型
let isDone: boolean = false;
// 编译通过
// 后面约定,未强调编译错误的代码片段,默认为编译通过
注意,使用构造函数
Boolean创造的对象不是布尔值
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
// 后面约定,注释中标出了编译报错的代码片段,表示编译未通过
- 事实上
new Boolean()返回的是一个Boolean对象:
let createdByNewBoolean: Boolean = new Boolean(1);
- 直接调用
Boolean也可以返回一个boolean类型:
let createdByBoolean: boolean = Boolean(1);
- 在
TypeScript中,boolean是JavaScript中的基本类型,而Boolean是JavaScript中的构造函数。其他基本类型(除了null和undefined)一样
大小写这条规则我建议直接记成一句话:小写的永远是你要的那个。String、Number、Boolean 这三个大写形态在业务代码里几乎没有正当用途,看到就该怀疑是写错了。有些 lint 规则(typescript-eslint 的 no-wrapper-object-types)会直接把它们标成错误。
# 2.1.2 数值
使用
number定义数值类型