基础精粹

类型注意事项

数组类型

有两种类型注解方式,特别注意第二种使用 TS 内置的 Array 泛型接口。

let arr1: number[] = [1, 2, 3];
// 下面就是使用 TS 内置的 Array 泛型接口来实现的
let arr2: Array<number | string> = [1, 2, 3, "abc"];

元组类型

元组是一种特殊的数组,限定了数组元素的个数和类型

let tuple: [number, string] = [0, "1"];

需要注意元组的越界问题,虽然可以越界添加元素,但是仍然是不能越界访问,强烈不建议这么使用

tuple.push(2); // 不报错
console.log(tuple); // [0, "1", 2] 也能都打印出来
console.log(tuple[2]); // 但是想取出元组中的越界元素,就会报错元组长度是2,在index为2时没有元素

函数类型

函数类型可以先定义再使用,具体实现时就可以不用注明参数和返回值类型了,而且参数名称也不用必须跟定义时相同。

对象类型

对象如果要赋值或者修改属性值,那么就不能用简单的对象类型,需要定义完整的对象类型

symbol 类型

symbol 类型可以直接声明为 symbol 类型,也可以直接赋值,跟 ES6 一样,两个分别声明的 symbol 是不相等的。

undefined 、null 类型

变量可以被声明为 undefined 和 null ,但是一旦被声明,就不能再赋值其他类型。

undefined 和 null 是任何类型的子类型,那就可以赋值给其他类型。但是需要设置配置项 "strictNullChecks": false

枚举类型

枚举分为数字枚举和字符串枚举,此外还有异构枚举(不推荐)

数字枚举

枚举既能通过名字取值,又能通过索引取值,我们具体看一下是怎么取到的。

字符串枚举

字符串枚举只能通过名字取值,不能通过索引取值。

常量枚举

用 const 声明的枚举就是常量枚举,会在编译阶段被移除。如下代码编译后 Month 是不产生代码的,只能在编译前使用,当我们不需要一个对象,但是需要一个对象的值的时候,就可以使用常量枚举,这样可以减少编译后的代码。

异构枚举

数字和字符串枚举混用,不推荐

枚举成员注意点

  • 枚举成员是只读的,不能修改重新赋值

  • 枚举成员的分为 const member 和 computer member

  • 常量成员(const member),包括没有初始值的情况、对已有枚举成员的引用、常量表达式,会在编译的时候计算出结果,以常量的形式出现在运行时环境

  • 计算成员(computer member),需要被计算的枚举成员,不会在编译阶段进行计算,会被保留到程序的执行阶段

  • 在 computed member 后面的枚举成员,一定要赋一个初始值,否则报错

  • 含字符串成员的枚举中不允许使用计算值(computer member),并且在字符串枚举成员后面的枚举成员必须赋一个初始值,否则会报错(见上面的异构类型)

  • 数字枚举中,如果有两个成员有同样索引,那么后面索引会覆盖前面的(见下面的枚举 number )

枚举和枚举成员作为单独的类型

有以下三种情况,(1)枚举成员都没有初始值、(2)枚举成员都是数字枚举、(3)枚举成员都是字符串枚举

  • 变量定义为数字枚举类型,赋值任意 number 类型的值都是可以的(可以超出枚举定义的数字范围),对枚举没有影响,但是不能赋值字符串等。

  • 不同的枚举类型是不能比较的,不过同一个枚举类型是可以比较的,但是同一个枚举类型的不同枚举成员是不能比较的

  • 变量定义为枚举类型,甚至就算定义为枚举类型的某个具体成员的类型,赋值也是对枚举没有影响的。(如下,E 和 F 的结果还是不变的)

  • 字符串枚举类型的赋值,只能用枚举成员,不能随意赋值。(如果下 F)

接口类型

接口约束对象、函数、类的结构

对象类型接口

对象冗余字段

对象类型接口直接验证有冗余字段的对象字面量时会报错,这种冗余字段有时是不可避免的存在的。

解决方法一:在外面声明变量 result ,然后把 result 传入 render 函数,避免传入对象字面量。

解决方法二: 用类型断言(两种 as 和尖括号),但是如果对象字面中都没有符合的,还是会报错,可以用 as unknown as xxx

解决方法三:用字符串索引签名

接口属性可定义为只读属性和可选属性

可索引类型

不确定一个接口中有多少属性时,可以使用可索引类型。分为数字索引签名和字符串索引签名,如果接口定义了某一种索引签名的值的类型,之后再定义的属性的值必须是签名值的类型的子类型。可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。

函数类型接口

混合接口

混合接口,需要注意看一下,接口中的属性没有顺序之分,混合接口不需要第一个属性是匿名函数。

接口继承

函数类型相关

定义 TS 函数的四种方式,第一种方式可以直接调用,但是后三种就需要先实现定义的函数再调用

可选参数

可选参数必须位于必选参数之后,即可选参数后面不能再有必选参数

参数默认值

带默认值的参数不需要放在必选参数后面,但如果带默认值的参数出现在必选参数前面,必须明确的传入 undefined 值来获得默认值。在所有必选参数后面的带默认值的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。

函数重载

要求定义一系列的函数声明,在类型最宽泛的版本中实现重载, TS 编译器的函数重载会去查询一个重载的列表,并且从最开始的一个进行匹配,如果匹配成功,就直接执行。所以我们要把大概率匹配的定义写在前面。

函数重载的声明只用于类型检查阶段,在编译后会被删除。

类属性和方法注意点

  • 类属性都是实例属性,不是原型属性,而类方法都是原型方法

  • 实例的属性必须具有初始值,或者在构造函数中初始化,除了类型为 any 的。

类的继承

派生类的构造函数必须包含“super”调用,并且访问派生类的构造函数中的 this 之前,必须调用“super"

类修饰符

1、public: 所有人可见(默认)。

2、 private: 私有属性

私有属性只能在声明的类中访问,在子类或者生成的实例中都不能访问,但是 private 属性可以在实例的方法中被访问到,因为也相当于在类中访问,但是子类的的实例方法肯定是访问不到的。

可以把类的 constructor 定义为私有类型,那么这个类既不能被实例化也不能被继承

3、 protected 受保护属性

受保护属性只能在声明的类及其子类中访问,但是 protected 属性可以在实例的方法中被访问到,因为也相当于在类中访问

可以把类的 constructor 定义为受保护类型,那么这个类不能被实例化,但是可以被继承,相当于基类

4、 readonly 只读属性

只读属性必须具有初始值,或者在构造函数中初始化,初始化后就不能更改了,并且已经设置过初始值的只读属性,也是可以在构造函数中被重新初始化的。但是在其子类的构造函数中不能被重新初始化。

5、 static 静态属性

只能通过类的名称调用,不能在实例和构造函数或者子类中的构造函数和实例中访问,但是静态属性是可以继承的,用子类的类名可以访问

注意:构造函数的参数也可以添加修饰符,这样可以将参数直接定义为类的属性

抽象类

只能被继承,不能被实例化的类。

在抽象类中可以添加共有的方法,也可以添加抽象方法,然后由子类具体实现

接口类

  • 类实现接口时,必须实现接口的全部属性,不过类可以定义自己的属性

  • 接口不能约束类的构造函数,只能约束公有成员

接口继承类

相当于把类的成员抽象出来,只有类的成员结构,但是没有具体实现

接口抽离类成员时不仅抽离了公有属性,还抽离了私有属性和受保护属性,所以非继承的子类都会报错

被抽象的类的子类,也可以实现类抽象出来的接口,而且不用实现这个子类的父类已有的属性

泛型

泛型函数

注意:用泛型定义函数类型时的位置不用,决定是否需要指定参数类型,见下面例子。

泛型函数例子

泛型接口

注意:泛型接口的泛型定义为全局时,实现必须指定一个参数类型,或者用带默认类型的泛型

泛型类

注意:泛型不能应用于类的静态成员。并且实例化时,不指定类型,就可以传入任何类型

泛型约束

约束泛型传入的类型

泛型总结

  • 利用泛型,函数和类可以轻松地支持多种类型,增强程序的扩展性

  • 不必写多条函数重载,冗长的联合类型声明,增强代码可读性

  • 灵活控制类型之间的约束

类型检查机制

类型检查机制: TypeScript 编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。其作用是辅助开发,提高开发效率

类型推断

类型推断: 指的是不需要指定变量的类型(函数的返回值类型),TypeScript 可以根据某些规则自动地为其推断出一个类型

基础类型推断

最佳通用类型推断

当需要从多个类型中推断出一个类型的时候,TypeScript 会尽可能的推断出一个兼容当前所有类型的通用类型

上下文类型推断

以上的推断都是从右向左,即根据表达式推断,上下文类型推断是从左向右,通常会发生在事件处理中。

类型断言

在确定自己比 TS 更准确的知道类型时,可以使用类型断言来绕过 TS 的检查,改造旧代码很有效,但是防止滥用。

类型兼容

当一个类型 Y 可以被赋值给另一个类型 X 时,我们就可以说类型 X 兼容类型 Y

X兼容Y:X(目标类型) = Y(源类型)

接口兼容

成员少的兼容成员多的

函数兼容性

1、参数个数

固定参数

目标函数的参数个数一定要多于源函数的参数个数

Handler 目标函数,传入 test 的 参数函数 就是源函数

可选参数和剩余参数

(1) 固定参数是可以兼容可选参数和剩余参数的

(2) 可选参数是不兼容固定参数和剩余参数的,但是可以通过设置"strictFunctionTypes": false 来消除报错,实现兼容

(3) 剩余参数可以兼容固定参数和可选参数

2、参数类型

基础类型

接口类型

接口成员多的兼容成员少的,也可以理解把接口展开,参数多的兼容参数少的。对于不兼容的,也可以通过设置"strictFunctionTypes": false 来消除报错,实现兼容

3、返回值类型

目标函数的返回值类型必须与源函数的返回值类型相同,或者是其子类型

4、函数重载

函数重载列表(目标函数)

函数的具体实现(源函数)

目标函数的参数要多于源函数的参数才能兼容

返回值类型不兼容

枚举类型兼容性

枚举类型和数字类型是完全兼容的

枚举类型之间是完全不兼容的

类的兼容性

和接口比较相似,只比较结构,需要注意,在比较两个类是否兼容时,静态成员和构造函数是不参与比较的,如果两个类具有相同的实例成员,那么他们的实例就相互兼容

私有属性

类中存在私有属性情况有两种,如果其中一个类有私有属性,另一个没有。没有的可以兼容有的,如果两个类都有,那两个类都不兼容。

如果一个类中有私有属性,另一个类继承了这个类,那么这两个类就是兼容的。

泛型兼容

泛型接口

泛型接口为空时,泛型指定不同的类型,也是兼容的。

如果泛型接口中有一个接口成员时,类型不同就不兼容了

泛型函数

两个泛型函数如果定义相同,没有指定类型参数的话也是相互兼容的

兼容性总结

  • 结构之间兼容:成员少的兼容成员多的

  • 函数之间兼容:参数多的兼容参数少的

类型保护机制

指的是 TypeScript 能够在特定的区块(类型保护区块)中保证变量属于某种特定的类型。可以在此区块中放心地引用此类型的属性,或者调用此类型的方法。

前置代码,之后的代码在此基础运行

实现 getLanguage 方法直接用 lang.helloJava 是不是存在作为判断是会报错的

利用之前的知识可以使用类型断言解决

类型保护第一种方法,instanceof

类型保护第二种方法, in 可以判断某个属性是不是属于某个对象

类型保护第三种方法, typeof 类型保护,可以帮助我们判断基本类型

类型保护第四种方法,通过创建一个类型保护函数来判断对象的类型

类型保护函数的返回值有点不同,用到了 is ,叫做类型谓词

总结

不同的判断方法有不同的使用场景:

  • typeof:判断一个变量的类型(多用于基本类型)

  • instanceof:判断一个实例是否属于某个类

  • in:判断一个属性是否属于某个对象

  • 类型保护函数:某些判断可能不是一条语句能够搞定的,需要更多复杂的逻辑,适合封装到一个函数内

高级类型

交叉类型

& 符号。虽然叫交叉类型,但是是取的所有类型的并集

联合类型

声明的类型并不确定,可以为多个类型中的一个,除了可以是 TS 中规定的类型外,还有字符串字面量联合类型、数字字面量联合类型

对象联合类型

对象的联合类型,只能取两者共有的属性,所以说对象联合类型只能访问所有类型的交集

可区分的联合类型

这种模式是结合了联合类型和字面量类型的类型保护方法,一个类型如果是多个类型的联合类型,并且每个类型之间有一个公共的属性,我们就可以凭借这个公共属性来创建不同的类型保护区块。

核心是利用两种或多种类型的共有属性,来创建不同的代码保护区块

下面的函数如果只有 Square 和 Rectangle 这两种联合类型,没有问题,但是一旦扩展增加 Circle 类型,类型校验就不会正常运行,而且也不报错,这个时候我们是希望代码有报错提醒的。

如果想要得到正确的报错提醒,第一种方法是设置明确的返回值,第二种方法是利用 never 类型.

第一种方法是设置明确的返回值

第二种方法是利用 never 类型,原理是在最后 default 判断分支写一个函数,设置参数是 never 类型,然后把最外面函数的参数传进去,正常情况下是不会执行到 default 分支的。

索引类型

索引类型的查询操作符

keyof T 表示类型 T 的所有公共属性的字面量的联合类型

索引访问操作符

T[K] 表示对象 T 的属性 K 所代表的类型

泛型约束

T extends U 泛型变量可以继承某个类型获得某些属性

先看如下代码片段存在的问题。

解决如下

映射类型

可以从一个旧的类型,生成一个新的类型

以下代码用到了 TS 内置的映射类型

条件类型

T extends U ? X : Y

分布式条件类型

(A | B) extends U ? X : Y 等价于 (A extends U ? X : Y) | (B extends U ? X : Y)

用法一:利用分布式条件类型可以实现 Diff 操作

用法二:在 Diff 的基础上实现过滤掉 null 和 undefined 的值。

以上的类型别名在 TS 的类库中都有内置的类型

  • Diff => Exclude<T, U>

  • NotNull => NonNullable<T>

此外,内置的还有很多类型,比如从类型 T 中抽取出可以赋值给 U 的类型 Extract<T, U>

比如: 用于提取函数类型的返回值类型 ReturnType<T>

先写出 ReturnType<T> 的实现,infer 表示在 extends 条件语句中待推断的类型变量。

分析一下上面的代码,首先要求传入 ReturnType 的 T 必须能赋值给一个最宽泛的函数,之后判断 T 能不能赋值给一个可以接受任意参数的返回值待推断为 R 的函数,如果可以,返回待推断返回值 R ,如果不可以,返回 any 。

创建运维管理主文件夹,创建地域管理和主机集合的文件夹和路由访问文件

Last updated