基础类型

boolean

number

string

undefined 和 null 类型

默认情况下 nullundefined 是所有类型的子类型。可以把 null 和 undefined 赋值给 number 类型的变量。

但是如果指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自,不然会报错。

any、unknown 和 void 类型

any

不清楚用什么类型,可以使用 any 类型。

不建议使用 any,不然就丧失了 TS 的意义。

unknown 类型

不建议使用 any,当我不知道一个类型具体是什么时,可以使用 unknown 类型,unknown 类型代表任何类型,它的定义和 any 定义很像,但是它是一个安全类型,使用 unknown 做任何事情都是不合法的。

 

比如,这样一个 divide 函数,把 param 定义为 unknown 类型 ,TS 编译器就能拦住潜在风险,如下图,

因为不知道 param 的类型,使用运算符 /,导致报错。再配合类型断言,即可解决这个问题

类型断言(Type Assertion)可以用来手动指定一个值的类型。

断言语法:<类型>值值 as 类型

void

void类型与 any 类型相反,它表示没有任何类型。比如函数没有明确返回值,默认返回 Void 类型

never 类型

never类型表示的是那些永不存在的值的类型

有些情况下值会永不存在,比如:

never 类型是任何类型的子类型,也可以赋值给任何类型。

没有类型是 never 的子类型,没有类型可以赋值给 never 类型

数组类型

元组类型

数组中元素的数据类型都一般是相同的(any[] 类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组。

声明一个元组并初始化:

或者我们可以先声明一个空元组,然后再初始化:

元组中元素使用索引来访问,语法格式如下:

可以对元组使用数组的方法,比如使用 push 时,不会有越界报错

函数类型

TS 定义函数类型需要定义输入参数类型和输出类型。

输出类型也可以忽略,因为 TS 能够根据返回语句自动推断出返回值类型。

函数没有明确返回值,默认返回 Void 类型

可选参数

参数后加个问号,代表这个参数是可选的,注意可选参数要放在函数入参的最后面,不然会导致编译错误。

默认参数

跟 JS 的写法一样,在入参里定义初始值。

函数赋值

JS 中变量随便赋值没问题,但在 TS 中函数不能随便赋值,会报错的。

也可以用下面这种方式定义一个函数 add3,把 add2 赋值给 add3

有点像 es6 中的箭头函数,但不是箭头函数,TS 遇到 : 就知道后面的代码是类型

函数重载

函数重载是指两个函数名称相同,但是参数个数或参数类型不同

比如实现一个 add 函数,如果传入参数都是数字,就返回数字相加,如果传入参数都是字符串,就返回字符串拼接。

在 TS 中,实现函数重载,需要多次声明这个函数,前几次是函数定义,列出所有的情况,最后一次是函数实现,需要比较宽泛的类型,比如上面的例子就用到了 any。

interface 接口

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

定义 interface 一般首字母大写,代码如下:

注意:interface 不是 JS 中的关键字,所以 TS 编译成 JS 之后,interface都会被删除掉,interface 只是在 TS 中用来做静态检查。

可选属性

跟函数的可选参数是类似的,在属性上加个 ?,这个属性就是可选的,比如下面的 age 属性

只读属性

如果希望某个属性不被改变,可以这么写:

描述函数类型

interface 也可以用来描述函数类型,代码如下:

duck typing(鸭子类型)

interface 还有一个响亮的名称: duck typing(鸭子类型)。

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

举个例子:

FunctionWithProps 接口描述了一个函数类型,还向这个函数类型添加了 name 属性,这看上去完全是四不像,但是这个定义是完全可以工作的。React 的 FunctionComponent(函数式组件) 就是这么写的

基本写法

定义一个 Person 类,有属性 name 和 方法 speak

继承

使用 extends 关键字实现继承,定义一个 Student 类继承自 Person 类。

注意,上例中 Student 类没有定义自己的属性,可以不写 super ,但是如果 Student 类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。比如,Student 类新增一个 grade(成绩) 属性,就要这么写:

private/public/protected

public,公有的,一个类里默认所有的方法和属性都是 public。

private,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。 protected 受保护的,继承它的子类可以访问,实例不能访问

将 Person 类的 name 属性改为 protected。

实例访问 name 属性,会报错:类可以访问。

static

static 是静态属性,可以理解为是类上的一些常量,实例不能访问。

实例访问,会报错

抽象类

所谓抽象类,是指只能被继承,但不能被实例化的类,抽象类用一个 abstract 关键字来定义

抽象类有两个特点:

定义一个 Dog 类,继承自 Animal 类

多态

多态指的是,父类定义一个抽象方法,在多个子类中有不同的实现,运行的时候不同的子类就对应不同的操作,比如:

Dog 类和 Cat 类都继承自 Animal 类,Dog 类和 Cat 类都不同的实现了 sayHi 这个方法。

this类型

类的成员方法可以直接返回一个 this,这样就可以很方便地实现链式调用。

在继承的时候,this 可以表示父类型,也可以表示子类型

这样就保持了父类和子类之间接口调用的连贯性。

implements

interface 同样可以用来约束 class,要实现约束,需要用到 implements 关键字。

比如手机有播放音乐的功能,可以这么写:

定义了约束后,class 必须要满足接口上的所有条件。如果 Cellphone 类上不写 playMusic 方法,会报错。

 

不同的类有一些共同的属性和方法,使用继承很难完成。使用 implements,问题就会迎刃而解。

这样 Car 类和 Cellphone 类都约束了播放音乐的功能。

枚举

TS 中我们使用 const 来声明常量,但是有些取值是在一定范围内的一系列常量。

比如一周有七天,比如方向分为上下左右四个方向。

这时就可以使用枚举(Enum)来定义。他有两个特点:

枚举成员会被赋值为从 0 开始递增的数字,

枚举会对枚举值到枚举名进行反向映射,

如果枚举第一个元素赋有初始值,就会从初始值开始递增,

手动赋值

但有时候后端给你返回的数据状态是乱的,就需要我们手动赋值。比如后端说 Buy 是 100,Send 是 20,Receive 是 1,就可以这么写,

实际开发中经常会有这种情况发生。

枚举中的成员可以被计算,比如经典的使用位运算合并权限,可以这么写,

常量枚举

使用 const 来定义一个常量枚举

常量枚举不允许包含计算成员 常量枚举可以避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问。

类型推论

定义时不赋值

定义时不赋值,就会被 TS 自动推导成 any 类型,之后随便怎么赋值都不会报错。

初始化变量

因为赋值的时候赋的是一个字符串类型,所以 TS 自动推导出 userName 是 string 类型。

这个时候,再更改 userName 时,就必须是 string 类型,是其他类型就报错

设置默认参数值

函数设置默认参数时,也会有自动推导,比如定义一个打印年龄的函数,默认值是 18

那么 TS 会自动推导出 printAge 的入参类型,传错了类型会报错。

函数返回值

决定函数返回值时, TS 也会自动推导出返回值类型。比如一个函数不写返回值,

TS 自动推导出返回值是 void 类型

内置类型

内置对象是指根据标准在全局作用域 global 上存在的对象,这里的标准指的是 ECMAcript 和其他环境(比如DOM)的标准。

JS 八种内置类型

ECMAScript 的内置对象

比如,ArrayDateError 等,

DOM 和 BOM

比如 HTMLElementNodeListMouseEvent

 

TS 进阶

高级类型

联合类型

如果希望一个变量可以支持多种类型,就可以用联合类型(union types)来定义。

一个变量既支持 number 类型,又支持 string 类型,就可以这么写:

如果直接访问 length 属性,string 类型上有,number 类型上没有,就报错了。

交叉类型

要对对象形状进行扩展,可以使用交叉类型 &

比如 Person 有 name 和 age 的属性,而 Student 在 name 和 age 的基础上还有 grade 属性,就可以这么写,

交叉类型和 interface 的 extends 非常类似,都是为了实现对象形状的组合和扩展。

类型别名(type)

类型别名(type aliase),听名字就很好理解,就是给类型起个别名。

类型别名的用法如下,

type 和 interface 的区别

两者相同点:

两者不同点:

类型保护

使用 typeof 关键字判断变量的类型。之所以叫类型保护,就是为了能够在不同的分支条件中缩小范围,这样我们代码出错的几率就大大降低了。

如果有一个 getLength 函数,入参是联合类型 number | string,返回入参的 length,

这么写会报错,因为 number 类型上没有 length 属性。

我们把 getLength 方法改造一下,就可以精准地获取到 string 类型的 length 属性了,

类型断言

使用类型断言来告诉 TS,我(开发者)比你(编译器)更清楚这个参数是什么类型,你就别给我报错了

类型断言语法(tsx 语法):

注意,类型断言不是类型转换,把一个类型断言成联合类型中不存在的类型会报错。

泛型

可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

泛型就出现了,它可以轻松解决输入输出要一致的问题。

基本使用

泛型的语法是 <> 里写类型参数,一般可以用 T 来表示。

我们在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 arg:T 和输出 T 中即可使用了。

这样,我们就做到了输入和输出的类型统一,且可以输入输出任何类型。

泛型中的 T 就像一个占位符,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出

type 和 interface 都可以定义函数类型,也用泛型来写一下,type 这么写:

interface 这么写:

默认参数

如果要给泛型加默认参数,可以这么写:

这样默认就是 number 类型了

约束泛型

假设现在有这么一个函数,打印传入参数的长度,我们这么写:

因为不确定 T 是否有 length 属性,会报错。

可以和 interface 结合,来约束类型。

这其中的关键就是 <T extends ILength>,让这个泛型继承接口 ILength,这样就能约束泛型。

索引类型

keyof(索引查询)

keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

上面的例子,Test 类型变成了一个字符串字面量。

T[K](索引访问)

T[K],表示接口 T 的属性 K 所代表的类型,

extends (泛型约束)

T extends U,表示泛型变量可以通过继承某个类型,获得某些属性

这样入参就一定要有 length 属性,比如 str、arr、obj 都可以, num 就不行。

 

改造后,从对象中抽取一些属性的值,然后拼接成数组,可以这么写:

映射类型

Partial

Partial<T>T的所有属性映射为可选的,使用 Partial 改造一下,就可以变成可选属性,

in 操作符,用来对联合类型实现遍历

Partial 的实现用到了 in 和 keyof

Readonly

Readonly<T>T的所有属性映射为只读的,例如:

Partial 原理几乎完全一样

Pick

Pick用于抽取对象子集,挑选一组属性并组成一个新的类型

这样就把 name 和 age 从 IPerson 中抽取出来。

Record

上面三种映射类型官方称为同态,意思是只作用于 obj 属性而不会引入新的属性

Record 是会创建新属性的非同态映射类型。

条件类型

T extends U ? X : Y

若类型 T 可被赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型

Exclude

Exclude 意思是不包含,Exclude<T, U> 会返回 联合类型 T 中不包含 联合类型 U 的部分。

Extract

Extract<T, U>提取联合类型 T 和联合类型 U 的所有交集。

工具类型

Omit

Omit<T, U>从类型 T 中剔除 U 中的所有属性。

Parameters

Parameters 获取函数的参数类型,将每个参数类型放在一个元组中。

ReturnType

ReturnType 获取函数的返回值类型。

 

声明文件

declare

当使用第三方库时,很多三方库不是用 TS 写的,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

比如,在 TS 中直接使用 Vue,就会报错,这时,我们可以使用 declare 关键字来定义 Vue 的类型

这样就不会报错了,使用 declare 关键字,相当于告诉 TS 编译器,这个变量(Vue)的类型已经在其他地方定义了,你直接拿去用,别报错。

仅仅会用于编译时的检查,在编译结果中会被删除。

.d.ts

通常我们会把声明语句放到一个单独的文件(Vue.d.ts)中,这就是声明文件,以 .d.ts 为后缀。

// src/Vue.d.ts

// src/index.ts

一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 Vue.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 Vue 的类型定义了。