前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版

Typescript总结篇(二) 语法全景与工程配置

首页2018-12-30 12:30:14Front-End
TypeScriptJavaScript前端工程化

写完上一篇把 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 代码,反过来不成立。

TypeScript ES6 ES5 三者的超集包含关系示意图

这张图能解释一件事,为什么迁移成本比想象中低。.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文件

生成出来的配置文件是这样,里面大部分选项都是注释掉的,可以当成一份带说明的清单来读。

tsc --init 生成的 tsconfig.json 配置文件内容

这个json文件里有很多选项

  • target是选择编译到什么语法
  • module则是模块类型
  • outDir则是输出目录,可以指定这个参数到指定目录

更多细节 https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html

接下来我们需要开启监控了,在vscode任务栏中

在任务栏里选 tsc: 监视,编译器就常驻后台,改一次保存编译一次。

VS Code 中开启 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 定义数值类型

fe
  • 一、简介
    • 1.1 什么是 TypeScript
    • 1.2 为什么选择 TypeScript
    • 1.3 安装 TypeScript
    • 1.4 Hello TypeScript
  • 二、基础
    • 2.1 原始数据类型
      • 2.1.1 布尔值
      • 2.1.2 数值
      • 2.1.3 字符串
      • 2.1.4 空值
      • 2.1.5 Null 和 Undefined
    • 2.2 任意值Any
    • 2.3 类型推论
    • 2.4 联合类型
    • 2.5 对象的类型和接口
      • 2.5.1 简单例子
      • 2.5.2 可选属性
      • 2.5.3 任意属性
      • 2.5.4 只读属性
    • 2.6 数组的类型
      • 2.6.1「类型 + 方括号」表示法
      • 2.6.2 数组泛型
      • 2.6.3 用接口表示数组
      • 2.6.4 any 在数组中的应用
      • 2.6.5 类数组
    • 2.7 函数的类型
      • 2.7.1 函数声明
      • 2.7.2 函数表达式
      • 2.7.3 用接口定义函数的形状
      • 2.7.4 参数默认值
      • 2.7.5 剩余参数
      • 2.7.6 函数重载
    • 2.8 类型断言
    • 2.9 声明文件
      • 2.9.1 声明(declare)语句
      • 2.9.2 声明文件(约定.d.ts后缀)
      • 2.9.3 第三方声明文件
    • 2.10 内置对象
      • 2.10.1 ECMAScript 的内置对象
      • 2.10.2 DOM 和 BOM 的内置对象
      • 2.10.3 TypeScript 核心库的定义文件
      • 2.10.4 用 TypeScript 写 Node.js
  • 三、进阶
    • 3.1 类型别名
    • 3.2 字符串字面量类型
    • 3.3 元组
      • 3.3.1 简单的例子
      • 3.3.2 越界的元素
    • 3.4 枚举
      • 3.4.1 简单的例子
      • 3.4.2 手动赋值
      • 3.4.3 常数项和计算所得项
      • 3.4.4 常数枚举
      • 3.4.5 外部枚举
    • 3.5 类
      • 3.5.1 类的概念
      • 3.5.2 public private 和 protected
      • 3.5.3 抽象类
      • 3.5.4 类的类型
    • 3.6 类与接口
      • 3.6.1 类实现接口
      • 3.6.2 接口继承接口
      • 3.6.3 接口继承类
      • 3.6.4 混合类型
    • 3.7 泛型
      • 3.7.1 简单的例子
      • 3.7.2 多个类型参数
      • 3.7.3 泛型约束
      • 3.7.4 泛型接口
      • 3.7.5 泛型类
      • 3.7.6 泛型参数的默认类型
    • 3.8 声明合并
      • 3.8.1 函数的合并
      • 3.8.2 接口的合并
      • 3.8.3 类的合并
  • 四、工程
    • 4.1 tsconfig.json
    • 4.2 TypeScript 编译
  • 五、一些例子演示
    • 5.1 定义ajax请求数据接口
    • 5.2 函数类型接口-对方法约束
    • 5.3 可索引接口 数组和对象的约束(不常用)
      • 5.3.1 可索引接口-对数组的约束
      • 5.3.2 可索引接口-对对象的约束
      • 5.3.3 类类型接口 对类的约束
    • 5.4 接口的扩展
    • 5.5 泛型类接口
      • 5.5.1 泛型类 泛型方法
      • 5.5.2 泛型接口
  • 总结
  • 参考

← Typescript+React模板搭建(三)从零配到可上线Typescript基础及结合React实践(一) 从零搭出可跑的项目 →