TypeScript 的常用知识点

2019 年 12 月 4 日 0 条评论 1.95k 次阅读 2 人点赞

前言

在学习 TS 之前,需要理解它的两个特点:

  1. TS 是 JS 的超集
  2. TS 给 JS 带来类型系统

这意味着 TS 的根基是 JS,是在 JS 的 基础上添加了类型系统。

类型声明

类型系统的一个特点是使用类型声明。

一文学会 TypeScript 的 82% 常用知识点(上)

观察上述代码,在 JS 中,使用「等号=」给变量赋值

经过 TS 的扩展,可以使用「冒号:」给变量赋类型。代码中声明变量 foo 的类型是 string。

类型校验

类型系统的另一个特点是进行类型校验。

在 TS 中,需要校验「等号=」左右的类型是否匹配。

一文学会 TypeScript 的 82% 常用知识点(上)

如上述代码所示,值存在明确的类型,TS 会校验左右两侧的类型是否匹配,若不匹配则提示错误。

在了解这些前置知识后,来看看具体的 TS 知识点。

一、基本类型

继承 JS 的基本类型如:string、number、boolean、undefined、null。扩展了其他基本类型如:any、never、void。

1.1 string

表示类型为字符串

一文学会 TypeScript 的 82% 常用知识点(上)

1.2 number

表示类型为数字

一文学会 TypeScript 的 82% 常用知识点(上)

1.3 boolean

表示类型为布尔类型

一文学会 TypeScript 的 82% 常用知识点(上)

1.4 undefined

表示类型为 undefined

一文学会 TypeScript 的 82% 常用知识点(上)

1.5 null

表示类型为 null

一文学会 TypeScript 的 82% 常用知识点(上)

1.6 void

表示类型为 undefined 或 null。

1.6.1 对于变量

一个声明为 void 类型的变量,只能被赋值为 undefined 或 null。这种应用场景较少。

一文学会 TypeScript 的 82% 常用知识点(上)

1.6.2 对于函数

void 用于表示函数没有返回值,只是执行某些操作。这是 void 的普遍应用场景。

一文学会 TypeScript 的 82% 常用知识点(上)

一个没有显式返回的函数,默认 return undefined。符合 void 类型只能被赋值为 undefined 或 null。

1.7 never

表示永远不存在值。

典型例子:一个只会抛出异常的函数,它的返回值并不存在。

一文学会 TypeScript 的 82% 常用知识点(上)

执行函数则抛出错误,连 undefined 都不会返回。

1.8 any

表示可能为任何类型。

典型例子:不确定变量的类型或类型是动态的。

一文学会 TypeScript 的 82% 常用知识点(上)

二、引用类型

TS 中的引用类型,除了 JS 中的数组类型、对象类型,还扩展了枚举类型,元祖类型。

2.1 数组类型

表示由某(些)类型组成的数组。有两种写法:方括号表示法和尖括号表示法。

2.1.1 方括号表示法

一文学会 TypeScript 的 82% 常用知识点(上)

表示 arr 的类型是由数字组成的数组。

2.1.2 尖括号表示法

使用 Array<元素类型>,这是数组泛型的使用形式,关于泛型后续会展开。

一文学会 TypeScript 的 82% 常用知识点(上)

2.2 只读数组

使用 ReadonlyArray<属性类型> 定义只读数组。只读数组只能在数组初始化时定义其值,创建后不能进行修改。

一文学会 TypeScript 的 82% 常用知识点(上)

只读数组和常规数组类型 Array<T> 类似。区别在于:常规数组存在修改数组的方法,只读数组不存在修改数组的方法。

2.3 object

表示类型为对象。除了 string、number、bigint、boolean、 undefined、null、symbol 基本类型外的引用类型。

一文学会 TypeScript 的 82% 常用知识点(上)

2.4 元组

定长数组构成,数组中的元素是某(些)类型。

一文学会 TypeScript 的 82% 常用知识点(上)

2.5 枚举

使用关键字 enum 定义枚举类型。默认枚举值从 0 开始,可以手动赋值。

一文学会 TypeScript 的 82% 常用知识点(上)

三、类型断言

明确告诉 TS 某个值的类型。有两种写法:尖括号写法、as 写法。

尖括号写法

一文学会 TypeScript 的 82% 常用知识点(上)

as 写法

一文学会 TypeScript 的 82% 常用知识点(上)

注意事项

类型断言和类型转换是有明确区别,不能将它理解成类型转换。

在阐述此注意事项之前,先引入另一个概念:联合类型

一文学会 TypeScript 的 82% 常用知识点(上)

在上述代码中,string | number 表示的就是一个联合类型,意味着 answer 的类型可以为 string 或 number。

下面逐步解释类型断言和类型转换之间的区别。

一文学会 TypeScript 的 82% 常用知识点(上)

上述代码中,string | number 联合类型包含 string 类型,所以这两种类型之间存在联系。使用 as 将联合类型断言成更加具体的 string 类型。

一文学会 TypeScript 的 82% 常用知识点(上)

上述代码中,将 number 类型断言为 string | number 类型。同样是因为两种类型之间存在联系,所以也允许断言。相当于将一个具体的 number 类型断言成更加广泛的联合类型。

一文学会 TypeScript 的 82% 常用知识点(上)

而将 string 类型断言为 number 类型,这是两种不同的类型,没有任何联系,断言会提示错误。

观察上述三个例子,断言发生在两种类型存在联系的情况,它并不是将一种类型转换成另一种类型。

有些脑瓜子灵活的朋友会想到,借助两次断言来进行类似的类型转换。

一文学会 TypeScript 的 82% 常用知识点(上)

这是欺骗了 TS 校验,后果只能自己承担。

四、接口

上面提到使用 object 描述对象类型。但是,对于具有复杂结构的对象、函数。上述的 object 类型力有不逮。

一文学会 TypeScript 的 82% 常用知识点(上)

上述代码中,即使是空对象,也能通过 object 类型校验,而不会校验对象的结构是否能满足后续使用。

此时,就需要使用接口。在 TS 中,接口是描述值的结构

一文学会 TypeScript 的 82% 常用知识点(上)

上述代码中,就定义了一个接口来描述参数 o,要求它是一个对象,含有 name 属性,且属性值是字符串。

所以,不符合此结构的空对象 {} 就提示错误。

一般来说,使用关键字 interface 来定义接口。

重写上述接口 ——

一文学会 TypeScript 的 82% 常用知识点(上)

4.1 固定属性

在接口中,使用 属性名:属性类型 的结构定义固定属性。

一文学会 TypeScript 的 82% 常用知识点(上)

如上述代码所示,传入的对象需要具有 name 和 age 两个属性,否则会报错。

4.2 可选属性

在接口中,使用 属性名?:属性类型 的结构定义可选属性。顾名思义,可选属性可以存在,也可以不存在。

一文学会 TypeScript 的 82% 常用知识点(上)

4.3 只读属性

在接口中,使用 readonly 属性名:属性类型 的结构定义只读属性。只读属性只能在属性初始化时定义其值,定义后不能进行修改。

一文学会 TypeScript 的 82% 常用知识点(上)

4.4 额外检查

TS 会对对象字面量进行额外的属性检查

一文学会 TypeScript 的 82% 常用知识点(上)

在上述代码中,接口 Point 定义了两个可选属性,对象字面量中属性 x 和接口兼容,属性 z 是多余无意义的。

虽然实际的属性比接口定义的多,按照常规理解,这是可以通过类型校验,但事实却相反。

TS 对于对象字面量是会进行额外的属性检查,体现在:

当对象字面量赋值给变量或它直接作为参数传递给函数时,如果对象字面量的属性没有在接口中定义,则会报错。

换句话说,对于对象字面量,当它直接赋值给变量和函数参数时,它的属性不能比接口描述的多。

这里重点是直接赋值,如果像例子中,先将对象字面量赋值给变量,再通过变量传参,这样间接的方法可以绕过额外的检查。

4.5 可索引类型

可索引类型包括字符串索引类型数字索引类型

4.5.1 字符串索引类型

字符串索引类型具有字符串索引签名

一文学会 TypeScript 的 82% 常用知识点(上)

4.5.2 数字索引类型

数字索引类型具有数字索引签名

一文学会 TypeScript 的 82% 常用知识点(上)

4.5.3 混合索引签名

属性和索引签名可以形成混合索引签名,但是属性需要和索引签名类型匹配。

一文学会 TypeScript 的 82% 常用知识点(上)

另外,TS 允许同时使用上述两种签名,但是数字索引返回值的类型,它必须是字符串索引返回值类型的子类型。

因为对于 JS 来说,当使用数字索引时,会将它转换成字符串进行索引。所以它们需要保持一致。

一文学会 TypeScript 的 82% 常用知识点(上)

上述代码中,ThreeD 是 TwoD 的子类型,所以接口 PointA 正确。

4.5.4 只读索引签名

可以将索引签名设置为只读,只能在数组初始化时定义其值,创建后不能进行修改。

一文学会 TypeScript 的 82% 常用知识点(上)

4.6 函数类型

函数类型具有调用签名

一文学会 TypeScript 的 82% 常用知识点(上)

如上述代码所示,调用签名包括参数列表和返回值类型。

对于函数类型来说,它校验的值当然是函数 ——

一文学会 TypeScript 的 82% 常用知识点(上)

如上述代码所示,函数的参数名可以与签名的参数名不同。关键是对应位置的参数类型需要相同。

4.7 类类型

这里的概念有些复杂,如果有良好的 JS 基础,会较易理解。

类的关键字是 Class,由 ES6 开始引入,并逐步完善。本质上 Class 属于语法糖,是基于 prototype 原型链实现的。

一文学会 TypeScript 的 82% 常用知识点(上)

而无论是 ES5 或 ES6,属性 age 是在创建的实例上,而方法 getAge() 是在实例的原型链上。

一文学会 TypeScript 的 82% 常用知识点(上)

而类本身,它是不存在 age 属性 和 getAge() 方法的。

一文学会 TypeScript 的 82% 常用知识点(上)

当然,我们也可以给类本身定义属性和方法。 但给类本身定义的属性和方法并不能通过实例直接访问。

一文学会 TypeScript 的 82% 常用知识点(上)

所以,类与实例的属性和方法是割裂的。

TS 将描述类的属性和方法部分称为类的静态部分类型,将描述实例的属性和方法部分称为类的实例部分类型

一文学会 TypeScript 的 82% 常用知识点(上)

在上述代码中,使用关键字 implements 描述类实现了接口。更准确的是:描述 类的实例部分 实现了接口。

实现该接口的类的实例,它是具有 age 属性,getAge() 方法。而类本身(即类的静态部分)具有何种属性与方法,上述代码没有进行定义。

所以,这里所说的类类型,实际上是类(实例的)类型

关于如何定义类的静态部分的类型,在后续会详细介绍。

4.8 接口继承

接口的可以使用关键字 extends 定义继承。一个接口可以继承多个接口。

一文学会 TypeScript 的 82% 常用知识点(上)

4.9 混合类型

一个对象可能混合多种类型。

例如定义一个带版本号的函数——

一文学会 TypeScript 的 82% 常用知识点(上)

值得注意,上述使用类型断言,将函数断言为 Fn,即使 fn 在创建时并不存在 version 属性。

4.10 接口继承类

当接口继承类类型时候,表现在继承类的成员和结构,但不包括其实现。

接口继承类的一个场景是,定义一个子类的类类型。

一文学会 TypeScript 的 82% 常用知识点(上)

五、类

类在上篇有所提及,这里作进一步了解。

类的典型结构

包括属性、方法、构造函数。使用关键字 new 创建类的实例。

一文学会 TypeScript 的 82% 常用知识点(下)

5.1 类的继承

使用关键字 extends 定义类的继承。

一文学会 TypeScript 的 82% 常用知识点(下)

5.2 类的成员修饰符

5.2.1 public

类的成员修饰符默认是 public,也可以显式声明。

一文学会 TypeScript 的 82% 常用知识点(下)

上述代码中,属性 a 和 b 均被声明为公共的。

5.2.2 private

将类的成员声明为私有的,即只有类内部访问。

一文学会 TypeScript 的 82% 常用知识点(下)

TS 是结构性类型系统(鸭式辨形),如果两种类型的结构相同,一般情况下,TS 会认为它们是兼容的。

一文学会 TypeScript 的 82% 常用知识点(下)

可是,当类的成员带有 private 或 protected 修饰符时,情况却不一样。

一文学会 TypeScript 的 82% 常用知识点(下)

上述代码中,虽然看上去它们的结构相同。但是,当一个类的成员中存在 private 或 protected 时,如果需要另一个类与它兼容,则需要两个类的声明来源于同一份声明,例如通过继承。

一文学会 TypeScript 的 82% 常用知识点(下)

5.2.3 protected

修饰符 protected 和 private 类似,区别在于使用 protected 修饰的成员,在派生类可以访问。

一文学会 TypeScript 的 82% 常用知识点(下)

5.2.4 readonly

修饰符 readonly 将属性设置为只读。

一文学会 TypeScript 的 82% 常用知识点(下)

将属性设置为只读,只能在初始化时候定义其值,定义后不能修改。

5.2.5 static

在类类型中,我们区分了类的静态部分和类的实例部分。修饰符 static 将类成员声明为静态成员。

一文学会 TypeScript 的 82% 常用知识点(下)

5.2.6 抽象类

我们创建基类时,如果不希望基类能被实例化,可以使用 protected 修饰符。

一文学会 TypeScript 的 82% 常用知识点(下)

更普遍的做法是,使用 abstract 修饰符。abstract 能定义抽象类和抽象类中的抽象方法。

一文学会 TypeScript 的 82% 常用知识点(下)

5.2.7 getter 和 setter

很多时候,对某个属性读取时,同时需要对其进行处理,例如。

一文学会 TypeScript 的 82% 常用知识点(下)

更加简单的办法是,通过 存储器 处理。

getter 和 setter 统称存取器,其实在 ES5 就已经存在,很多朋友没有留意。如果对存取器不熟悉的,也可以通过 TS 来理解。

一文学会 TypeScript 的 82% 常用知识点(下)

六、函数

在 TS 中,有几种方式定义函数类型。

6.1 函数签名

利用上文提到的函数签名来定义函数类型。

一文学会 TypeScript 的 82% 常用知识点(下)

6.2 完整的函数类型

除了使用函数签名外,还可以直接对函数进行类型描述。

一文学会 TypeScript 的 82% 常用知识点(下)

但是,这种方式毫无疑问是太繁琐了,TS 存在类型推断的功能,它能根据等号一侧已存在的函数类型,而推断出另一侧的函数类型,从而简化代码。

6.3 类型推断

函数推断简化代码

一文学会 TypeScript 的 82% 常用知识点(下)

6.4 可选参数

在 JS 中,参数都是可选的,如果函数声明了参数,但是实际没有传参,则参数值是 undefined。

一文学会 TypeScript 的 82% 常用知识点(下)

默认情况下, TS 要求参数是必须的,函数声明了参数,就应该传进对应的参数。

一文学会 TypeScript 的 82% 常用知识点(下)

但确实存在某个参数可能存在,可能不存在的情况。这时候就需要用到可选参数了。

一文学会 TypeScript 的 82% 常用知识点(下)

值得注意的是,可选参数必须跟在必须参数之后。

6.5 默认参数

在 ES6 中,可以给参数设定默认值。

一文学会 TypeScript 的 82% 常用知识点(下)

而在 TS 中,默认参数的特性也是和 ES6 一致,而默认参数的类型是:默认值和 undefined 的联合类型。

一文学会 TypeScript 的 82% 常用知识点(下)

6.6 剩余参数

在 ES6 中,引入了剩余参数,代表由剩余所有参数组成的参数数组。

一文学会 TypeScript 的 82% 常用知识点(下)

在 TS 中给剩余参数添加类型,和普通参数接近,区别在于剩余参数肯定是数组类型。

一文学会 TypeScript 的 82% 常用知识点(下)

6.7 函数重载

函数根据传入不同的参数而返回不同的数据,这是十分普遍的情况。

一文学会 TypeScript 的 82% 常用知识点(下)

如何对这种情况进行类型描述呢?有些朋友可能会想到使用联合类型进行处理。

一文学会 TypeScript 的 82% 常用知识点(下)

但是,这样不能很好地描述输入和输出类型的关系。

在上述例子中,参数和返回存在对应关系:参数是 number 类型,则返回 boolean,参数是 boolean 类型,则返回 number,其他则返回 undefined。

此时,我们可以运用函数重载描述这些对应关系:

一文学会 TypeScript 的 82% 常用知识点(下)

上述代码中,定义了三个 foo 函数的重载,需要注意两点。

  1. 具体的函数声明不属于重载部分,即 function foo(a: any): any 并不是重载,而是具体的函数声明,所以例子中只有三个函数重载;
  2. 当匹配重载列表时候,是根据定义时候的顺序由上到下匹配的。在例子中,传入参数是 undefined 时,首先尝试匹配 number,然后尝试匹配 boolean,最后尝试匹配 any,匹配成功则使用 any 这个重载定义。

7 泛型

泛型其实并不高深,借助几个已知概念可以帮助理解。

7.1 泛型函数

上文提到,当函数的参数和返回的类型存在联系时,我们使用了函数重载进行类型描述。

假如希望函数的参数和返回的值,它们之间的类型一致。如果使用函数重载,则需要:

一文学会 TypeScript 的 82% 常用知识点(下)

这显得十分繁琐。而且,当传入的参数是数组或其他对象时,匹配的是函数重载的 object 部分,返回值的类型也是 object,丢失了原有的结构。

我们期望即使传入复杂的结构对象,也保留它们的类型描述。像上述代码中,传入是 number[] 类型的参数,期望返回的也是 number[] 类型描述,而不是 object 类型描述。

而使用泛型就能解决这个问题。以下借助变量的概念来对泛型进行解释。

变量我们都清楚,当变量被赋值成某值或对象时,该变量就代表着某值或对象。

而在类型系统中,通过引入类型变量,达到和变量相同的效果:当类型变量被赋值成某类型,则类型变量就代表该类型。

一文学会 TypeScript 的 82% 常用知识点(下)

上述代码中,在函数名后添加 <T>, 代表在类型系统中声明了类型变量 T。所以,在对参数 a 和返回值进行类型描述时,我们可以使用 T 这个类型变量。

另外,上述代码使用字母 T 代表类型变量,并不是固定的,也可以使用字母 A、a 等合法的变量名。只不过约定俗成,T 代表着 Type(类型),这样其他人阅读代码也一目了然。

既然是变量,我们怎么给这个类型变量传参呢?

一文学会 TypeScript 的 82% 常用知识点(下)

可以清晰地看到,在调用函数时,使用尖括号传入具体的类型值。以 foo<number[]>([1, 2, 3) 为例,相当于应用 function<number[]>(a: number[]): number[] 类型校验。这样,在参数和返回值中都保留了明确的数据结构。

上文我们提到类型推断,同样在泛型中也可以应用。

一文学会 TypeScript 的 82% 常用知识点(下)

上述代码中,省略了明确的类型传参,但 TS 会根据我们传入的参数的类型,自动确定 T 的类型。

7.2 泛型接口

我们知道,接口是具有函数签名的,形如:

一文学会 TypeScript 的 82% 常用知识点(下)

接口中的函数签名也存在泛型的形式:

一文学会 TypeScript 的 82% 常用知识点(下)

另外,还可以将泛型参数作为接口的参数:

一文学会 TypeScript 的 82% 常用知识点(下)

7.3 泛型类

泛型类和泛型接口类似,声明泛型参数后,在类中就可以使用。

一文学会 TypeScript 的 82% 常用知识点(下)

雷雷

这个人太懒什么东西都没留下

文章评论(0)

(Spamcheck Enabled)