TypeScript 的类型系统会对数据进行类型检查,它可以在编译阶段规避不必要的错误,并且语义化清晰,有助于代码的阅读。
TypeScript 类型检查机制包含三个部分:
- 类型推断
- 类型保护
- 类型兼容性
一、never 和 unknow
never 类型表示那些永远不存在的类型 return throw Error() 或则 不可取值
unknown 类型是 any 类型的对应的安全类型 unknown 只可以分配给any unknown
二、类型推断
不需要指定变量类型或函数的返回值类型,TypeScript 可以根据一些简单的规则推断其的类型
1、基础类型推断
初始化变量,设置默认参数和决定返回值时
2、最佳通用类型推断
从多个元素类型推断出一个类型时,TypeScript 会尽可能推断出一个兼容所有类型的通用类型 let x = [1, 'imooc', null] => let x: (string | number | null)[]
3、上下文类型推断
上下文类型推断则是从左向右的类型推断
class Animal {
public species: string | undefined
public weight: number | undefined
}
const simba: Animal = {
species: 'lion',
speak: true // Error, 'speak' does not exist in type 'Animal'
}
三、类型断言
TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型, 覆盖它的推断, 慎用,编译时语法,不涉及运行时
1、关键字 as 覆盖其类型推断
2、 首尾标签<>
interface User {
nickname: string;
admin: boolean;
groups: number[]
}
const user = <User>{};
3、非空断言!
编译器不能够去除 null 或 undefined,可以使用非空断言 !
手动去除
function fixed(name: string | null):string {
return name!.charAt(0) || '';
}
4、双重断言 极少有应用场景
const user = 'Even' as any an User;
四、 类型兼容性
确定一个类型是否能赋值给其他类型
1、结构化
TypeScript 类型兼容性是基于结构类型的;结构类型只使用其成员来描述类型。
2、比较函数
判断两个函数是否兼容,首先要看参数是否兼容,第二个还要看返回值是否兼容。
3、枚举的类型兼容性
//同一个枚举 枚举与数字类型相互兼容
enum Status {
Pending,
Resolved,
Rejected
}
let current = Status.Pending
let num = 0;
current = num
num = current;
//枚举类型之间是不兼容
eunm Color {Red, Bulue, Green}
let current = Status.Pending
current = Color.Red // Error
4、类的类型兼容性
- 比较两个类类型数据时,只有实例成员会被比较,静态成员和构造函数不会比较
- 类的私有成员和受保护成员会影响兼容性
class Animal {
feet!: number; // 实例成员 如feet 是受保护的,下面的赋值操作不可成功
contructor(name:string, numFeet:number){}
}
class Size {
feet!: number; // 实例成员
contructor(numFeet:number){}
}
class Dog extends Animal {}
// 类 Animal 和类 Size 有相同的实例成员 `feat` 属性,且类型相同
let a:Animal
let s:Size
let d:Dog
//a = s!; //ok feet 为protected feet!: number => Error
//s = a; //ok
// 父类之所以能够给赋值给子类,是因为子类中没有成员
//a = d! //ok
//d = a; //ok
5、范型的类型兼容性
如果没有指定泛型类型的泛型参数,会把所有泛型参数当成 any 类型比较. 当指定类型后泛型的类型兼容性根据其是否被成员使用而不同,被使用时赋值失败。
interface NotEmpty<T> {
data: T // 如果不使用 ok
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y! // Error //如果不使用data ok
let identity = function<T>(x: T): void {
// ...
}
let identity1 = function<T>(y: T): void {
// ...
}
identity = identity1 //ok
四、类型保护
类型保护是指缩小类型的范围,在一定的
块级作用域内
由编译器推导其类型
,提示并规避不合法
的操作
1、 typeof 判断变量类型
if(type target === 'string'){
target.tofixed(2) // Error, is not number
target.split('')// true
}
2、instanceof
typeof 判断基础类型,instanceof 判断是否为某个对象的实例
3、 in 确定属性是否存在于某个对象
class User {
public nickname: string | undefined
public groups: number[];
}
function typeGuard(arg: User | Log) {
if('nickname' in arg) {
arg.nickname = 'imooc';
}
}
五、infer 关键字 声明类型变量
在条件类型表达式中,可以在
extends
条件语句中使用infer
关键字来声明一个待推断的类型变量infer
的作用是让TypeScript
自己推断,并将推断的结果存储到一个类型变量中,infer
只能用于extends
语句中。
1、ReturnType 理解 infer
ReturnType<T>
– 获取函数返回值类型
const add = (x:number, y: number) => x+y;
type t = ReturnType<typeof add> // type t = number;
来看一下 ReturnType 的实现源码:
type ReturnType<T extends (...args:any)=> any> = T extends (...args: any) => infer R ? R : any
如果 `T` 满足约束条件 `(...args: any) => any`,TypeScript 推断出函数的返回值, 并且能够赋值给 `(...args: any) => infer R`, 借助 `infer` 关键字将其储存在类型变量 `R` 中,则返回类型为 `R`,否则为 any 类型
3. 借助 infer 实现元组转联合类型 [string, number] -> string | number
type Flatten<T> = T extends Array<infer U> ? U : never;
type T0 = [string, number]
type T1 = Flatten<T0> // string | number;
如果泛型参数 T
满足约束条件 Array<infer U>
,那么就返回这个类型变量 U
。
七、接口interface 和 类型别名 type
都是对数据类型进行约束。
区别:
1、定义类型不同:
类型别名type可以作用于原始值(string、boolean等),联合类型(string|number),元组([string, number]) 交叉类型(string & number)以及其它任何你需要手写的类型.
interface 声明对象、函数或者类时,先定义接口,确保其数据结构的一致性 一般为一个对象结构。
2、扩展或继承方式不同
interface 可以实现extends(继承拥有) implements(打印 必须复制实现)
type 通过 联合 交叉 方式扩展
3、是否创建新类型
-
类型别名并不会创建新类型,是对原有类型的引用,而接口会定义一个新类型。