TS系列基础篇(一) TS类型指南
TS 系列基础篇(一) TS 类型指南
这段时间以来,TS 的发展可谓是如日中天,本想偷个懒去看看别人写的分享贴来学习,找了近十篇之后,发现要么是比较浅显,要么有些偏差,没有找到很满意的。于是决定去看官方文档。学习了一段时间后,准备写一个系列,从基础类型,对象,函数,模块等 TS 知识,到在vue
、react
中的应用,供有需要的同学们参考。其中,我套用了不少官方文档的示例,觉得我的学习经验不好或不正确的朋友,欢迎批评指正。
TS
是JS
的超集。在学 TS 之前,最好有一定的JS
基础。本篇只介绍typescript
的安装和各种基础类型。需要了解其它内容的同学可以看其它篇章或查阅官方文档。
[toc]
(一)、安装与编译
想要使用TS
,得先会安装。Typescript 需要node
环境,确保你已经安装了node
。如果还没有安装node
,可以去Node.js
官网下载,傻瓜式安装。
打开项目目录进行初始化:
1 | npm init -y |
官方推荐了npm
,yarn
,pnpm
三种工具,任选其一即可(npm
工具为node
自带的包管理工具,可自由使用;yarn
或pnpm
工具需要提前安装)。
1 | # with npm |
在安装 ts 时,编译工具tsc
也会被自动安装。待安装完成,在项目根目录下新建一个app.ts
。
1 | // app.ts |
即可通过以下任一方式运行tsc
,编译成功后会在和app.ts
同级目录下多出一个app.js
文件。
1 | # 当前目录下的app.ts文件编译为app.js |
app.js
:
1 | const str = "app"; |
这个js
文件和app.ts
看起来没有差别,这是因为我们没有在app.ts
里没有进行类型约束。与类型的相关内容会在后面谈到。现在我们来让app.ts
出一点“错误”,将str
换成数组,编辑器会把错误代码用红色波浪线标出,如果此时在命令行运行yarn tsc app.ts
,控制台便会报错。
1 | // app.ts |
尽管如此,报错了的代码依旧会被编译成js
文件。我们可以在tsc
命令后加上编译的相关配置指令来进行控制。比如,加上--noEmitOnError
之后,一旦报错便不会生成js
文件。
1 | tsc --noEmitOnError hello.ts |
但是 ts 的编译配置项非常多,如果每次都通过在命令行加入指令来进行相关控制,无疑非常繁琐。因此我们可以在tsconfig.json
里编写相关配置,这样我们执行tsc
命令时,编译器会默认从当前目录逐步向上层目录查找并读取tsconfig.json
里的配置项。
(二)、配置文件:tsconfig.json
在运行tsc
命令时,我们可以在后面添加指令来指定相关配置。但是我们会更倾向于在tsconfig.json
里对相关指令进行配置,以减少重复、繁琐的操作。在Vue
、React
等框架搭建的项目里,一般都已生成初步配置好了的tsconfig.json
文件。本篇只进行解基础内容的分享,有关配置的章节将在后续推出。
(三)、类型基础
这里介绍部分 TS 基础类型,关于类型的进阶将在后续篇章中单独介绍。注意不要将基础类型和 js 基本数据类型混为一谈。基础类型可以理解为 ts 内置的各种类型,而非我们人为定义出的类型。TS 有多种基础类型,这些类型可以用来进行组合,从而得到我们需要的人为定义的类型。TS 在声明变量时,在变量名后加上冒号: 和类型名来进行变量的类型注释。如果不添加类型注释,则 TS 会根据变量的初始值进行类型推论,自动推断出该变量属于什么类型。如果也没有初始值,则会被推断为any类型。
1. 原有的基本数据类型
string
:字符串类型,注意String
在js
里已经有特殊意义了,而小写的string
才是Typescript
用来表示字符串的类型名称,即在注释变量类型为字符串时,使用小写的string
,而不是大写的String
,注意不要混淆了两者;number
和boolean
同理。number
:数字类型;boolean
:布尔类型;1
2
3
4
5// 声明变量类型,可以不赋初值,后续给num赋的值必须是number类型
let num: number;
let str: string = "typescript";
// 类型推断:TS会自动推断出bool的类型为boolean
let bool = true;
2. Array
Array
是数组类型,属于对象类型的一种。由于数组内会有数组成员,因此,在声明数组变量的时候,还要给数组成员添加类型注释,一般有两种常见方式:Type[]
、Array<Type>
。后者涉及泛型概念,将在后续介绍。其中,Type
指代数组成员的类型,可以是基础类型,也可以是人为定义的类型 (关于数组的变形,元组类型,将在对象类型的章节介绍)。例如,要声明一个存放字符串的数组变量:
1 | let arr1: string[]; |
3. object
对象类型是我们平时更为常见的类型。在本篇只给出一些简单定义,后续篇章中会进行单独介绍。一个对象类型的变量可以通过键值对来存储多个数据。定义一个对象类型,可以简单地列出它的各个属性及属性的类型:
1 | // 定义一个包含name, age, gender属性的变量obj |
之后给 obj 赋值时必须有且只能有name
,age
,gender
三个属性,且属性值应为相应的类型。
1 | // 会报错,多了一个beauty属性,因此类型不合 |
如果想要让某个属性变为可选项,则可以在定义对象类型时在属性名后使用问号”?”:
1 | // 将gender定义为可选项 |
在某个属性被定义为可选项之后,一旦给该对象赋值时,没有传入该属性,它的取值便会成为undefined
(注意这与一开始边定义gender: 'gg' | 'mm' | undefined
不同。)
使用可选项有些地方需要注意,如在函数的形参中存在可选项,此时由于gender
属性可能为undefined
,我们在使用时需要在该属性后面加上英文感叹号”!
“进行非空断言,明确它不是undefined
。
1 | function fn(obj: { name: string; age: number; gender?: "gg" | "mm" }) { |
4. Union Types
联合类型
Union Types
是指使用 “|
“符号来把多个类型联合成一个类型,一个联合类型的变量,其值可以是联合类型的任何一个子类型。
1 | // 定义a为联合类型,则a可以是string类型也可以是number类型 |
在函数的形参中使用联合类型时有一些注意事项,如在上面的例子中,a
的类型是string | number
,此时a
无法调用字符串方法,因为a
有可能是一个number
;同理,也不能直接调用数字类型的方法。当然,也不能直接赋值给string
类型的变量或者number
类型的变量。
1 | function testTypes(input: string|number){ |
当然,如果每个子类型都具有共同的方法,则可以调用该共同的方法。例如:数组和字符串都具有slice
方法,则联合类型string | number[]
的变量可以调用slice
方法。
1 | function func(obj: string | number[]) { |
5. Type Alias
类型别名
使用type
关键字给你的类型起一个别名,以后就可以使用别名来指代这个类型。
1 | type Point = { |
6. Interfaces
通过关键字interface
,来定义一个接口,实际是一个对象类型,用于规定一个对象的形状。
1 | interface Point { |
简单说说interface
与类型别名的区别:
interface
可以通过extends
关键字来继承另一个interface
,而type
通过&
符号来连接不同的对象属性;
1 | interface Animal { |
interface
可以进行拓展,Type
不可以
1 | interface Animal { |
interface
定义对象的形状,type
不仅可以用于对象,也可以用于其它类型
1 | type TypeA = { |
7. Intersection Types
交叉类型
用 &
符号来连接多个类型,属于交叉类型 A & B
的变量,既满足A
的约束,又满足B
的约束。
1 | type TypeA = string | number; |
也可以用来拓展对象类型的属性:
1 | type A = { |
注意 &
和 | 的区别:”&
“可以合并多个对象类型的属性,使得到的新的对象类型包含其它所有类型的全部属性;”&
“可以获得多个类型之间的公共子类型;”|
“可以联合多个类型,得到的新类型的值,只需满足其中一种子类型即可。
8. Literal Types
字面量类型
通过字面量来定义类型,字面量的值可以是任意一个类型的值,可以将多个不同类型的字面量进行组合,此时得到的变量上的方法无法进行合法调用,因为变量可能为其它不含该方法的类型(与联合类型同理)。因此需要进行类型精简或类型断言。注意在变量声明时进行类型注释了的才能被字面量类型约束,如果没有类型注释,则会按照类型推论的结果来判定类型。
1 | // 定义gender只能取值为 '男' 或 '女' 中的一种 |
9. null
和 undefined
与 非空断言
两个空值类型,和在js
里的区别一致。开启/关闭严格空值检查会影响到空值类型的行为。当我们知道一个变量不会为空时,可以在该变量后使用英文感叹号 “!
“ ,进行临时非空断言 (Non-null Assertion
)。这点在函数中尤为重要。
1 | type MyType = string | number | undefined; |
10. Enums
枚举类型
枚举类型是一组被有意义地命名了的常量的集合。与其它类型本质上不同的是,其它的类型都只是类型,而枚举类型却是可以使用的值。通过enum
关键字声明某个变量为枚举类型的值,使用枚举类型,可以让我们不去关注变量实际的值,而使用更有意义的名字来代表实际的值。例如,在表示性别时,我们可以简单地用数字 1 和 2 来表示 男 和 女。那么在实际使用中,我们需要知道到底是 1 代表男还是 1 代表女。当数据从前端传到后端,后端的小伙伴又需要去了解哪个数字代表哪个性别。这对我们来说就不太友好。所以,我们可以使用枚举类型来定义一组表示性别的常量,之后使用时,只需取常量的名字即可。
1 | enum Gender { |
枚举类型包括数字型枚举、字符串型枚举、异构枚举等等。此处只简要了解一下枚举类型的的存在,后续会写一篇枚举类型的深入。
11. any
any
可以指代任何类型,可以被赋值给任意类型的变量。
1 | // 给变量anyscript一个any类型,其值为数字123 |
这个看起来很便捷的any
类型,在这种时候就会引发问题,造成类型污染。因此,我们应该避免使用any
,以免走进Anyscript
的误区。
12. unknown
与类型断言
unknown
用来表示未知类型,和any
相似,它的值可以是任何类型。不同的是,如果一个变量是unknown
类型,那么它在被明确为某个确切的类型之前,不能调用任何方法,也不能被赋值给其它变量。你可以使用类型断言来临时人为明确一个 unknown 变量的确切类型。毕竟你永远比Typescript
知道的多!类型断言一般有两种方式:使用 a as Type
或者 在需要进行类型断言的变量前使用尖括号:<Type>a
,来明确变量a
为Type
类型。注意类型断言是临时的,因此它不会改变原来unknown
变量的类型。
1 | // 声明一个unknown变量a,一个字符串变量b |
也许你会觉得使用unknown
类型有些繁琐。但相比起any
类型容易引发的错误,unknown
类型的使用足够安全。因此,如果有需要使用不明确的类型时,应该首选unknown
而不是any
。毕竟谁也不愿意,一杯茶,一个圈,一个BUG
改一天(甚至还在排查错误原因)。
13. never
和 void
void
用于表示函数返回空值;never
用于表示不该使用的值或者函数不应该有返回值,在我们平常的工作中never
的应用场景较少。
14.不常用的类型
Bigint
和Symbol
是ES6
之后加入的基本数据类型,目前在日常工作中的使用并不多见。TS
中的这两种类型和JS
中一致。
bigint
1
2
3
4
5// 使用BigInt函数来创建一个bigint类型的变量
const oneHundred: bigint = BigInt(100);
// 使用字面量语法 数字 + n 来创建bigint类型的变量
const anotherHundred: bigint = 100n;Symbol
Symbol
是ES6
之后新增的一种基本数据类型,每个Symbol
类型的变量,其值都是唯一的,即使传入相同的参数,返回的结果也永远不会相等。一般使用Symbol
函数来创建。1
2
3
4
5// 使用Symbol函数创建Symbol类型的变量/常量
const first1 = Symbol("1");
const first2 = Symbol("1");
first1 === first2; // 永远是false
类型基础的内容就介绍到这里啦,下一篇将着重介绍在函数中使用各种类型时需要注意的问题,例如如何进行类型精确。如果文章描述有不妥之处,恳请不吝指出,我们下一篇再见!