04-TypeScript 类型系统支持哪些类型和类型运算?
课程
1
如何阅读本小册
已学完
学习时长: 2分6秒
2
为什么说 TypeScript 的火爆是必然?
已学完
学习时长: 4分28秒
3
TypeScript 类型编程为什么被叫做类型体操?
已学完
学习时长: 9分16秒
4
TypeScript 类型系统支持哪些类型和类型运算?
已学完
学习时长: 19分27秒
5
套路一:模式匹配做提取
已学完
学习时长: 41分32秒
6
套路二:重新构造做变换
已学完
学习时长: 39分51秒
7
套路三:递归复用做循环
学习时长: 42分22秒
8
套路四:数组长度做计数
学习时长: 27分58秒
9
套路五:联合分散可简化
学习时长: 19分36秒
10
套路六:特殊特性要记清
学习时长: 36分41秒
11
类型体操顺口溜
学习时长: 27分26秒
12
TypeScript 内置的高级类型有哪些?
学习时长: 31分59秒
13
真实案例说明类型编程的意义
学习时长: 34分36秒
14
类型编程综合实战一
学习时长: 25分55秒
15
类型编程综合实战二
学习时长: 29分9秒
16
新语法 infer extends 是如何简化类型编程的
学习时长: 11分14秒
17
原理篇:逆变、协变、双向协变、不变
学习时长: 11分31秒
18
原理篇:编译 ts 代码用 tsc 还是 babel?
学习时长: 19分49秒
19
原理篇:实现简易 TypeScript 类型检查
学习时长: 47分51秒
20
原理篇:如何阅读 TypeScript 源码
学习时长: 19分27秒
21
原理篇:一些特殊情况的说明
已学完
学习时长: 12分39秒
22
小册总结
学习时长: 2分32秒
23
加餐:3 种类型来源和 3 种模块语法
学习时长: 17分6秒
24
加餐:用 Project Reference 优化 tsc 编译性能
已学完
学习时长: 4分47秒
juejin_logo copyCreated with Sketch.

学完前几节我们知道 TypeScript 给 JavaScript 加了一套静态类型系统,还支持了泛型和各种类型运算逻辑。

那么这个类型系统里都有哪些类型?支持哪些类型运算逻辑?

TypeScript 类型系统中的类型

静态类型系统的目的是把类型检查从运行时提前到编译时,那 TS 类型系统中肯定要把 JS 的运行时类型拿过来,也就是 number、boolean、string、object、bigint、symbol、undefined、null 这些类型,还有就是它们的包装类型 Number、Boolean、String、Object、Symbol。

这些很容易理解,给 JS 添加静态类型,总没有必要重新造一套基础类型吧,直接复用 JS 的基础类型就行。

复合类型方面,JS 有 class、Array,这些 TypeScript 类型系统也都支持,但是又多加了三种类型:元组(Tuple)、接口(Interface)、枚举(Enum)。

元组

元组(Tuple)就是元素个数和类型固定的数组类型:

type Tuple = [number, string];

接口

接口(Interface)可以描述函数、对象、构造器的结构:

对象:

interface IPerson {
    name: string;
    age: number;
}

class Person implements IPerson {
    name: string;
    age: number;
}

const obj: IPerson = {
    name: 'guang',
    age: 18
}

函数:

interface SayHello {
    (name: string): string;
}

const func: SayHello = (name: string) => {
    return 'hello,' + name
}

构造器:

interface PersonConstructor {
    new (name: string, age: number): IPerson;
}

function createPerson(ctor: PersonConstructor):IPerson {
    return new ctor('guang', 18);
}

对象类型、class 类型在 TypeScript 里也叫做索引类型,也就是索引了多个元素的类型的意思。对象可以动态添加属性,如果不知道会有什么属性,可以用可索引签名:

interface IPerson {
    [prop: string]: string | number;
}
const obj:IPerson = {};
obj.name = 'guang';
obj.age = 18;

总之,接口可以用来描述函数、构造器、索引类型(对象、class、数组)等复合类型

枚举

枚举(Enum)是一系列值的复合:

enum Transpiler {
    Babel = 'babel',
    Postcss = 'postcss',
    Terser = 'terser',
    Prettier = 'prettier',
    TypeScriptCompiler = 'tsc'
}

const transpiler = Transpiler.TypeScriptCompiler;

此外,TypeScript 还支持字面量类型,也就是类似 1111、'aaaa'、{ a: 1} 这种值也可以做为类型。

其中,字符串的字面量类型有两种,一种是普通的字符串字面量,比如 'aaa',另一种是模版字面量,比如 aaa${string},它的意思是以 aaa 开头,后面是任意 string 的字符串字面量类型。

所以想要约束以某个字符串开头的字符串字面量类型时可以这样写:

还有四种特殊的类型:void、never、any、unknown:

  • never 代表不可达,比如函数抛异常的时候,返回值就是 never。
  • void 代表空,可以是 undefined 或 never。
  • any 是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)。
  • unknown 是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。

这些就是 TypeScript 类型系统中的全部类型了,大部分是从 JS 中迁移过来的,比如基础类型、Array、class 等,也添加了一些类型,比如 枚举(enum)、接口(interface)、元组等,还支持了字面量类型和 void、never、any、unknown 的特殊类型。

类型的装饰

除了描述类型的结构外,TypeScript 的类型系统还支持描述类型的属性,比如是否可选,是否只读等:

interface IPerson {
    readonly name: string;
    age?: number;
}

type tuple = [string, number?];

TypeScript 类型系统中的类型运算

我们知道了 TypeScript 类型系统里有哪些类型,那么可以对这些类型做什么类型运算呢?

条件:extends ? :

TypeScript 里的条件判断是 extends ? :,叫做条件类型(Conditional Type)比如:

type res = 1 extends 2 ? true : false;

这就是 TypeScript 类型系统里的 if else。

但是,上面这样的逻辑没啥意义,静态的值自己就能算出结果来,为什么要用代码去判断呢?

所以,类型运算逻辑都是用来做一些动态的类型的运算的,也就是对类型参数的运算。

type isTwo<T> = T extends 2 ? true: false;

type res = isTwo<1>;
type res2 = isTwo<2>;

这种类型也叫做高级类型

高级类型的特点是传入类型参数,经过一系列类型运算逻辑后,返回新的类型。

推导:infer

如何提取类型的一部分呢?答案是 infer。

比如提取元组类型的第一个元素:

type First<Tuple extends unknown[]> = Tuple extends [infer T,...infer R] ? T : never;

type res = First<[1,2,3]>;

注意,第一个 extends 不是条件,条件类型是 extends ? :,这里的 extends 是约束的意思,也就是约束类型参数只能是数组类型。

因为不知道数组元素的具体类型,所以用 unknown。

infer 在后面的章节会大量用到,这里先简单了解即可。

联合:|

联合类型(Union)类似 js 里的或运算符 |,但是作用于类型,代表类型可以是几个类型之一。

type Union = 1 | 2 | 3;

交叉:&

交叉类型(Intersection)类似 js 中的与运算符 &,但是作用于类型,代表对类型做合并。

type ObjType = {a: number } & {c: boolean};

注意,同一类型可以合并,不同的类型没法合并,会被舍弃:

映射类型

对象、class 在 TypeScript 对应的类型是索引类型(Index Type),那么如何对索引类型作修改呢?

答案是映射类型

type MapType<T> = {
  [Key in keyof T]?: T[Key]
}

keyof T 是查询索引类型中所有的索引,叫做索引查询

T[Key] 是取索引类型某个索引的值,叫做索引访问

in 是用于遍历联合类型的运算符。

比如我们把一个索引类型的值变成 3 个元素的数组:

type MapType<T> = {
    [Key in keyof T]: [T[Key], T[Key], T[Key]]
}

type res = MapType<{a: 1, b: 2}>;

试一下

映射类型就相当于把一个集合映射到另一个集合,这是它名字的由来

除了值可以变化,索引也可以做变化,用 as 运算符,叫做重映射

type MapType<T> = {
    [
        Key in keyof T 
            as `${Key & string}${Key & string}${Key & string}`
    ]: [T[Key], T[Key], T[Key]]
}

我们用 as 把索引也做了修改,改成了 3 个 key 重复:

试一下

这里的 & string 可能大家会迷惑,解释一下:

因为索引类型(对象、class 等)可以用 string、number 和 symbol 作为 key,这里 keyof T 取出的索引就是 string | number | symbol 的联合类型,和 string 取交叉部分就只剩下 string 了。就像前面所说,交叉类型会把同一类型做合并,不同类型舍弃。

因为 js 处理对象比较多,所以索引类型的映射比较重要。

总结

给 JavaScript 添加静态类型系统,那肯定是能复用的就复用,所以在 TypeScript 里,基础类型和 class、Array 等复合类型都是和 JavaScript 一样的,只是又额外加了接口(interface)、枚举(enum)、元组这三种复合类型(对象类型、class 类型在 TypeScript 里叫做索引类型),还有 void、never、any、unkown 四种特殊类型,以及支持字面量做为类型。此外,TypeScript 类型系统也支持通过 readonly、?等修饰符对属性的特性做进一步的描述。

此外,TypeScript 支持对类型做运算,这是它的类型系统的强大之处,也是复杂之处。

TypeScript 支持条件、推导、联合、交叉等运算逻辑,还有对联合类型做映射。

这些逻辑是针对类型参数,也就是泛型(类型参数)来说的,传入类型参数,经过一系列类型运算逻辑后,返回新的类型的类型就叫做高级类型,如果是静态的值,直接算出结果即可,没必要写类型逻辑。

这些语法看起来没有多复杂,但是他们却可以实现很多复杂逻辑,就像 JS 的语法也不复杂,却可以实现很多复杂逻辑一样。

后面我们会大量用到这些类型编程语法来实现各种复杂的类型逻辑。

留言
Ctrl + Enter
全部评论(74)
我要全部都学的头像
删除
搬砖 @ 保密~
打卡
点赞
回复
宫秋的头像
删除
前端工程师
为什么不能直接这样type First<Tuple extends unknown[]> = Tuple[0]
点赞
1
删除
(作者)
可以啊,这里是另一种写法
点赞
回复
Asuka14024的头像
删除
前端工程师
温故知新,隔了一两个月再回来看依然可以学到之前没注意的点。真的是一本难得的好册子!
点赞
1
删除
(作者)
[碰拳]
点赞
回复
万不能青年的头像
删除
真的是很精粹的总结,大赞
点赞
回复
MarkTangCoding的头像
删除
全栈
在掘金买了几十个小册了,少有的质量不错的小册,以后继续支持。
1
1
删除
(作者)
[抱拳][西瓜]
点赞
回复
夏味的头像
删除
字面量类型这个例子报错了
点赞
2
删除
(作者)
你少了个 $
3
回复
删除
hhhhh
你少了个 $
点赞
回复
蒹葭问白露的头像
删除
前端 @ yy
复习继续打开,冲鸭
点赞
回复
CV专家的头像
删除
专业CV选手
infer介绍的好含糊,就几句话没看懂[捂脸]
1
回复
三郎mr的头像
删除
感谢分享
点赞
回复
o逍遥生o的头像
删除
@杭城西 @ 高级前端
传入类型参数,经过一系列类型运算逻辑后,返回新的类型的类型就叫做高级类型
点赞
回复
SteveCGC的头像
删除
大哥你搞前端,前端有啥用啊???
大哥,我想问下,写了.d.ts文件是不是就用不着import type了,感觉现在import type没用呢,但又看到好多地方用到这个
点赞
4
删除
(作者)
dts 是给没有类型声明的 js 模块添加类型声明的,之后用的话和普通的 ts 模块没啥区别
点赞
回复
删除
把所有的类型声明都放到dts文件中,合理吗
dts 是给没有类型声明的 js 模块添加类型声明的,之后用的话和普通的 ts 模块没啥区别
点赞
回复
查看更多回复
skyfury的头像
删除
node后端
推导:infer 小节里面的最后一个 unknown 打错了
点赞
回复
张小凡酱的头像
删除
字节跳动
void不可以是null吧,www.typescriptlang.org
点赞
1
删除
(作者)
额,写错了,我改改
1
回复
codermao的头像
删除
web实习 @ 小红书
语法本来也懂一些,但是看了现在理解更深刻一些。这种可编程的泛型真灵活
点赞
回复
苏菲的头像
删除
web前端工程师 @ 某游戏公司
整理成一张思维导图了~~[呲牙]
56
5
删除
太强了,
1
回复
删除
太强了 哥[思考]
太强了,
点赞
回复
查看更多回复
LoTwT的头像
删除
条件类型(Contional Type)拼错了,是 Conditional Type
2
回复
蒹葭问白露的头像
删除
前端 @ yy
& string 这比波 前后呼应,感觉念头通达了
3
1
删除
深有同感[赞]
点赞
回复
重威的头像
删除
`${Key & string}` 这个操作真的是学到了!
6
回复
小迷的头像
删除
前端
打卡,看了小册之后觉得还是自己的学习方法有问题啊
点赞
回复
爱吃鱼的桶哥Z的头像
删除
伪 · 全栈打杂攻城狮
光之打卡
点赞
回复
没有Peter你就是个d的头像
删除
可以的,打卡
点赞
回复
muyiyr的头像
删除
前端
所以可以理解为 Key & string 是为了限制类型为string吗?
点赞
4
删除
(作者)
是的,keyof 默认取出来的是 string | symbol | number, 和 string 取交叉类型就只剩下 string 部分了
1
回复
删除
那 as 之后 只能用 字符串类型是吗 number 和 symbol 类似似乎没法合并
是的,keyof 默认取出来的是 string | symbol | number, 和 string 取交叉类型就只剩下 string 部分了
点赞
回复
查看更多回复
明溪的头像
删除
因为索引类型(对象、class 等)可以用 string、number 和 symbol 作为 key,这里 keyof T 取出的索引就是 string | number | symbol 的联合类型,和 string 取交叉部分就只剩下 string 了。就像前面所说,交叉类型会把同一类型做合并,不同类型舍弃
----
/n
那为啥不写 & string 会报错捏?
[敲打]
展开
1
2
删除
(作者)
因为构成 string 的只能是 string 呀
点赞
回复
删除
这里没有js那种类型转换吗
因为构成 string 的只能是 string 呀
点赞
回复
atomic的头像
删除
前端工程师
心得:1. interface 中对类的继承突然出了兴趣,继承的实例拥有全部结果
2.| & 对比时感觉|后extends的匹配类型更宽:若干中有一个类型匹配上即可,&则要求全部匹配上
点赞
1
删除
到位,兄弟
点赞
回复
LeoFitz的头像
删除
前端工程师 @ 深圳某摸鱼公司
有一些看不懂,是我太菜了?[我想静静]
1
1
删除
(作者)
后面的案例会大量用到,跟着敲一敲就懂了
3
回复
新科的头像
删除
infer 是 对应指向{},[] 这两种里面的类型吗?
点赞
1
删除
(作者)
套路一主要讲这个,看完那篇就懂了
点赞
回复
BarrySong4Real的头像
删除
一个路过的前端 @ 浑水摸鱼
6666,很清晰写的[快哭了]
1
1
删除
(作者)
[碰拳]
点赞
回复
libmw的头像
删除
type x = 1 & 2;这种也没办法合并吧,虽然1和2都是相同类型。
点赞
4
删除
1 类型和 2类型 不是一个类型吧
点赞
回复
删除
是数字字面量
1
回复
查看更多回复
Doerme的头像
删除
FE @ 百度/YY
映射类型希望用的时候能记得住
点赞
1
删除
(作者)
看第二个套路
点赞
回复
时空等待的头像
删除
Nice
点赞
回复
chencs的头像
删除
前端开发工程
[呲牙]
点赞
回复
打痘痘的头像
删除
前端开发工程师
对新手很友好了 通俗易懂 非常想学习后面的内容[呲牙]
点赞
1
删除
(作者)
[碰拳]
点赞
回复
๓樛木的头像
删除
通俗易懂
点赞
1
删除
(作者)
[碰拳]
点赞
回复
ShiYi的头像
删除
前端开发工程师 @ shopee
看完打卡
点赞
回复
乌托邦的头像
删除
建议 联合类型、交叉类型什么的名词把对应的英文也加上
点赞
1
删除
(作者)
可以的
点赞
回复
hehe1111的头像
删除
漏写了个 n,unkown ->unknown
1
1
删除
(作者)
👌
点赞
回复
轩灵大大的头像
删除
前端/图形学/深度学习算法工程师 @ 某研究所/创新-数据智能部
我觉得这里说的不对啊,这里的extends是条件判断呀,是 extends ? : 呀
1
1
删除
(作者)
前面类型参数那个
2
回复
ShidongZhao的头像
删除
前端开发
光之呼吸二之型
2
回复