TS03 TypeScript进阶
TypeScript进阶知识学习笔记。
类型别名
概念
可以使用type
关键字来创建一个类型的别名:
1 | type num = number; |
类型别名常用于联合类型,例如字符串字面量类型,用来约束取值只能是几个字符串的一个
1 | type EventNames = 'click' | 'scroll' | 'mousemove'; |
与interface
的异同
二者的共同点是:
(1)都可以描述对象或者函数
(2)都允许扩展(extends
)
1 | type Name = { |
type
的扩展使用了&
二者的不同点是:
(1)type
可以声明基本类型的别名,interface
不可以
(2)type
语句可以使用typeof
获取实例的类型赋值
1 | // 当你想获取一个变量的类型时,使用 typeof |
(3)interface
能够声明合并,而type
不同
1 | interface User { |
元组
元组(Tuple)就是包含不同类型元素的数组:
1 | const person: [string, number] = ['Tom', 24]; |
可以赋值其中一项,但是如果对元组整体进行定义或者赋值的时候,必须提供所有元组类型中指定的项目:
1 | let person1: [string, number]; |
当添加元组规定的类型数量之外的元素时,元素的类型会被限定为元组包含类型的联合类型:
1 | const person: [string, number] = ['tom', 24]; |
枚举
定义枚举值
枚举类型用于取值被限定在一定范围的场景
1 | enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat} |
枚举成员会被赋值为从0
开始递增的数字,同时也会对枚举值到枚举名进行反向映射,上面的编译结果是:
1 | var Days; |
手动赋值
可以为枚举项手动赋值:
1 | enum Days {Sun = 'test', Mon = 1, Tue, Wed, Thu, Fri, Sat}; |
未手动赋值的枚举项会接着上一个枚举项递增,要注意的是,如果未手动赋值的枚举项和手动赋值的枚举项重复了,TypeScript是不会报错的。
计算所得项
枚举项有两种类型,常数项和计算所得项,上面的列子都是常数项,计算所得项如下:
1 | enum Color {Red, Green, Blue = "blue".length}; |
要注意,计算所得项后面不等接未手动赋值的项。
常数枚举项
常数枚举是使用const enum
定义的枚举类型:
1 | const enum Directions { |
常数枚举项会在编译阶段被删除,并且不能包含计算成员,上面的编译结果是:
1 | var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; |
外部枚举
使用declare enum
定义的枚举类象,之前提到的声明文件中的枚举类型就是这种:
1 | declare enum Directions { |
外部枚举定义的类型只会用于编译时的检查,编译结果中会被删除,上面的编译结果:
1 | var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right |
外部枚举一般都会出现在声明文件中
类
概念复习
(1)面向对象(OOP)有三大特性:
- 封装
将对数据的操作细节隐藏起来,只暴露对外的接口,调用者不需要知道细节,通过接口来访问对象,同时保证了外界无法任意更改对象内部的数据 - 继承
子类继承父类,子类拥有父类所有特性,还有属于自己的特性 - 多态
由继承产生了不同的类,对同一个方法有不同的响应(例如Cat
和Dog
都继承自Animal
,分别实现了自己的eat
方法)
(2)存取器
类的属性也有存取器即setter
和getter
,来改变属性的读取和赋值行为
(3)抽象类
抽象类是其他类继承的基类,抽象类不允许被实例化,抽象类中的抽象方法必须在子类中被实现。
(4)接口
不同类之间共有的属性和方法,可以抽象为一个接口,接口可以被类实现。一个类智能继承自另一个类,但是可以实现多个接口。
TypeScript中的访问修饰符
TypeScript可以使用三种访问修饰符,分别是:public
、private
和protected
(1)public
public
修饰的属性或方法是共有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的
(2)private
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问,子类中也是不允许的。当构造函数(constructor
)是private
时,这个类不允许被继承或者实例化
(3)protected
protected
修饰的属性或方法是受保护的,它与private
类似,区别是它在子类中是允许被访问的。当构造函数(constructor
)是protected
时,这个类不允许被实例化,只允许被继承。
1 | class Animal { |
修饰符还可以用在构造函数参数中,等同于在类中定义该属性,使代码更简洁:
1 | class Animal { |
readonly
表明一个属性是只读属性,只允许出现在属性声明或者索引前面中,如果readonly
和其他访问修饰符同时存在,readonly
要写在后面
1 | class Animal { |
抽象类
用abstract
来定义抽象类和其中的方法。
抽象类有一下几个特征:
(1)抽象类不允许被实例化
(2)抽象类中的抽象方法必须被子类实现,如果下面Man
中没有sayHi
方法,编译器就会报错
1 | abstract class Person { |
类与接口
接口可以用于对对象进行描述,同时,接口也可以对类的一部分行为进行抽象。
类实现接口
实现(implements)是面向对象中的一个重要概念。一般来说,一个类只能继承自另一个类,有时候不同的类之间有一些共有的特性,可以把这些特性提取成接口,用implements
关键字实现。
举例来说,门
是一个类,防盗门
是门
的子类。如果防盗门有一个报警器的功能,我们可以为防盗门
添加报警
的方法。同时,有另外一个类车
,它也有报警器的功能,就可以将报警
这个方法提取为一个接口,防盗门
和车
都去实现它
1 | interface Alarm { |
一个类可以实现多个接口:
1 | interface Alarm { |
接口继承
接口是可以继承接口的:
1 | interface Alarm { |
接口也可以继承类:
1 | class Alarm { |
混合类型
可以用接口来定义函数:
1 | interface SearchFn { |
有时候函数也可以有自己的属性和方法:
1 | interface SearchFn { |
泛型
定义泛型
泛型(Generics)是指在定义函数、接口或者类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。
例如下面的函数identity
,使用了类型变量T
:
1 | function identity<T>(arg: T): Array<T> { |
T
帮助我们捕获用户传入的类型,然后在后面就可以使用这个类型了。我们将T
作为返回值类型,这样保证了参数类型与返回值类型是相同的。
我们把上面的这种可以使用多个类型的函数叫做泛型,它可以适用于多个类型。不同于使用any
,它不会丢失信息。
定义泛型的时候,可以一次定义多个类型变量:
1 | function swap<T, U>(arr: [T, U]): [U, T] { |
使用泛型
定义了泛型后,有两种方法可以使用,第一中学是传入所有的参数,包括类型变量:
1 | let output = identity<string>("myString"); // type of output will be 'string' |
注意泛型变量的传递使用的是<>
而不是()
第二种方法更为普遍,利用类型推论,让编译器自动确定T
的类型
1 | let output = identity("myString"); // type of output will be 'string' |
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,必须把泛型变量当做任意类型,所以不能随意的操作它的属性或方法:
1 | function loggingIdentity<T>(arg: T): T { |
所以我们需要对泛型变量进行约束,使用extends
来继承一个接口,约束传入的T
必须符合接口的定义:
1 | interface LengthWise { |
注意,我们约束的是泛型变量,而不是参数,如果用下面的形式,实际不是进行泛型约束,而是约束的函数参数:
1 | // 或者用下面的形式,效果相同 |
多个类型参数之间也可以相互约束。
泛型接口
可以使用含有泛型的接口来定义函数的形状:
1 | interface CreateArrayFn { |
可以将泛型接口提前到接口名上:
1 | interface CreateArrayFn<T> { |
这时候再使用的时候,必须在接口后定义泛型的类型:
1 | const createArray: CreateArrayFn<any> = (length, value) => Array(length).fill(value) |
泛型类
与泛型接口相似,泛型也可以用于类的类型定义中:
1 | class Person<T> { |
类型参数的默认类型
在TypeScript2.3版本后,可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际参数中也无法推测出时,这个默认类型就会起作用:
1 | class Person<T = string> { |
声明合并
如果定义了两个相同名字的函数、接口或者类,他们会合并为同一个类型
函数的合并
函数的合并实际上就是函数的重载:
1 | function reverse(x: number): number; |
接口的合并
接口中的属性会简单的合并到一个接口中,但是前提是合并的属性的类型必须是唯一的:
1 | interface Alarm { |
接口中方法的合并,与函数的合并相同:
1 | interface Alarm { |
相当于:
1 | interface Alarm { |
类的合并
类的合并与接口的合并规则相同。
代码检查
ESLint
目前以及将来的TypeScript的代码检查方案是typeScript-eslint
TypeScript关注的类型的检查,而不是代码风格,代码风格的检查还是需要依靠ESLint。
在TypeScript中使用
安装:
1 | npm install --save-dev eslint typescript |
由于ESLint默认使用Espree进行语法解析,无法识别TypeScript的某些语法,需要安装@typescript-eslint/parser
替代默认的解析器
1 | npm install --save-dev @typescript-eslint/parser |
最后安装对应的规则插件@typescript-eslint/eslint-plugin
1 | npm install --save-dev @typescript-eslint/eslint-plugin |
对ESLint进行配置,在根目录下创建.eslintrc.js
:
1 | module.exports = { |
使用AlloyTeam的ESLint配置
推荐使用AloyTeam的ESLint规则中的TypeScript版本,安装和配置时直接按照下面的进行即可,已经包含了上面的安装和配置步骤
安装:
1 | npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy |
配置文件:
1 | module.exports = { |
检查.tsx
文件
要检查React的.tsc
文件,首先需要安装eslint-plugin-react
:
1 | npm install --save-dev eslint-plugin-react |
然后在lint
命令后面需要添加.tsx
后缀:
1 | { |
也可以使用AlloyTeam的ESLint规则的TypeScript React版本。
安装:
1 | npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy |
配置文件:
1 | module.exports = { |