TypeScript 系列(一):TS 核心概念
1 前言
昨天在掘金上看到了一篇 TypeScript5,是的,TS 都更新到 5 了,我还没开始正式使用。为了赶上时代的潮流,我尝试在自己写的小项目中使用了 TS,然而,直接裂开……于是就有了这个系列,我准备攻克这门技术,并把所思所想记录下来。
我打开了 TS 英文文档,等一下,如果你要问我,为什么打开英文文档,我想说我也很懒想看中文文档,可是中文文档有两点弊端:
- 中文更新不及时
- 存在部分内容不翻译
- 有些内容翻译过来就变了味
作为一个前端程序员,在 Get Started 中我选择 TypeScript for JavaScript Programmers。 这一篇教程告诉我们一件事:JS 的语法 TS 全有,TS 比 JS 多了一层类型系统(Type System):
-
类型推断(Types by Interface)
-
类型定义(Defining Types)
-
类型组合(Composing Types)
- 类型联合(Unions)
- 泛型(Generics)
-
结构化类型系统(Structural Type System)
以上就是 TS 与 JS 主要不同的地方,也是 TS 的主要概念,我们一个一个看。
2 类型推断(Types by Interface)
Types by Interface,通过推断而产生类型。啥意思呢?就是你和平时写 JS 一样去写 TS 的变量声明和赋值,TS 会根据值的类型去推断你声明的变量属于什么类型。例如:
1 被赋值给变量 a
,1 属于基础类型中的 number 类型,因此显而易见的是,变量 a
的类型就是 number。
这里可能会产生一个疑问:TS 帮我们推断变量类型的意义是什么?为了让我们像静态语言那样,固定变量的类型,从而减少不必要的 bug。
例如,在下方代码片段中, 变量 result
显式声明为 number 类型,然而 a + '1'
的结果是字符串 '11'
,不符合要求,这时 TS 的类型检测系统就会帮你发现问题,提示你出现错误了!
而 JS 一直以来都是一个动态类型语言,它的变量类型可以随意改动,于是乎,就会造成这样的结果:
3 类型定义(Defining Types)
TS 的第二个主要概念是类型定义,在 JS 当中会出现复杂的代码实现,这些代码的类型是很难被推断的。所以,TS 提供了接口(interface
)的概念用来描述这些复杂的定义。
3.1 字面量对象
例如,定义一个对象的接口 Person
,这个对象接口含有 name
和 age
两个属性,name
的类型必须是 string
,age
的类型必须是 number
。定义完成后,第 6 行的对象 p
的类型就是 Person
。
如果字面量对象与接口定义不一致,那么就会报错:
这就像口袋里只能放一只红球和一只蓝球,可你放了一只黄球和一只蓝球,当然不符合规则。
3.2 class
TS 也支持 class
语法和 OOP。
与字面量对象一样,如果传递的参数不符合规则,那么就会报错:
如果在没有在一开始定义构造器属性的类型,那么也会报错:
3.3 函数参数和返回值
TS 可以给函数的参数和返回值定义类型。
getPerson()
之后的Person
表示返回值需要符合 Person 类型;deletePeople(person: Person)
中的参数person
需要符合Person
类型。
3.4 TS 中的基础数据类型
除了 JS 的 7 种基础数据类型(string
, number
, bigint
, boolean
, undefined
, null
, symbol
),TS 还拓展了 unkown
、any
、never
、void
四种数据类型。
unknown
,未知类型,在你使用它的自行定义类型any
,任意类型never
,不可能到达的类型void
,函数无返回值的类型
4 类型组合(Composing Types)
使用 TS 可以将简单的类型组合成复杂的类型,一共有两种方案:
- 一个是 union,叫做类型联合
- 一个是 generics,叫做泛型
4.1 Union
类型联合是啥意思呢?就是某个类型可能是 A,可能是 B,可能 C。
类型联合以 type
开头来定义。
4.2 Generics
泛型给类型提供变量,让类型更加灵活,这就类似于“普通函数从没有入参到带了入参”的跃迁。
Array<string>
表示这是一个由string
类型数据组成的数组;Array<number>
表示这是一个由number
类型数据组成的数组;Array<{name: string}>
表示这是一个由接口对象{name: string}
数据组成的数组。
可以自己定义泛型,然后使用。举个例子,定义一本书的接口,书的价格 price
是由外部传入的(指的是 Type
),addPrice
的入参类型、getPrice
的返回值类型也全都是外部传入的。
第 9 行声明了一个 book1
的变量,它的类型是 Book<string>
,因此:
book1.price
类型就是string
book1.addPrice()
中的name
参数的类型就是string
book1.getPrice()
中的函数返回值类型就是string
所以,book2 的这三个类型又是什么呢?欢迎在评论区回答👏,哈哈。
5 结构类型系统(🦆 Structural Type System)
结构型类型系统,又称为 duck typing,鸭子类型。啥意思呢?只要它长得像鸭子,那它就是鸭子。
5.1 鸭子类型
对应的,在代码层面,只要这个对象包含了符合某个类型的属性并且类型相同,那么就说这个对象属于当前这个类型。换句话说,如果对象里有其他的属性存在并不会影响到对象的类型归属。
- 显然,
d1
符合Duck
类型 d2
中含有name
,color
属性并且类型符合Duck
中name
和color
,但是d2
还有一个age
属性,这个属性其实是Duck
没有的,但它仍然属于Duck
。d3
实际上和d2
是一样的,但是却报错了。原因在于,对象字面量只能和你定义好的属性配对上,但是age
不在其中。
那么,鸭子类型的意义是什么呢?当前后端联调时,前端通过接口拿到后端传递来的数据,这个数据一般就是 response.data.data
,这里面的数据结构并不一定和我们定义的接口类型完全一致,这时报错当然不符合情理。
5.2 class
在 class 中规则也是相同的。
6 总结
- TS 是 JS 的超集,在 JS 上架了一层类型系统
- TS 可以帮你自动推导出变量的类型
- 遇到比较复杂的对象,你可以使用接口 interface 来定义类型,其实就是手动推断(显式推断)
- 基础数据类型、字面量对象、class、函数参数和返回值,你能想到的标识符名称都能被赋予类型,还有 TS 特有的四种类型:
unkown
、any
、never
、void
- 如果需要将基础数据类型组合在一起,就使用类型联合
- 如果想要动态改变接口类型,那么就使用泛型
- 鸭子类型的理解很重要,有助于我们更好地写接口
TS 文档里其实还有很多内容,难以做到面面俱到,那么我们就从核心概念开始~另外,这篇文章比较偏概念,实践相关的内容将在后续章节呈现。
添加我的微信:enjoy_Mr_cat,共同成长,卷卷群里等你 🤪。
以上,如有谬误,还请斧正。
最后,希望这篇文章对你有所帮助,感谢您的阅读~