TypeScript 进阶学习

高级类型

交叉类型

操作符:&,交叉类型并不是交集,在 Typescript 里其实是并集的意思,即新生成的类型,会拥有所有子类型的特性

联合类型

操作符:|,联合类型必须做类型校验,如string | number,只能访问它们中共有的属性或者方法

keyof T

索引类型查询操作符。 对于任何类型 T, keyof T 的结果为 T 上已知的公共属性名的联合
node

1
2
3
4
5
let person: Person = {
name: 'aaa',
age: 35
};
// let personProps: keyof Person; // 'name' | 'age'

类型约束 extends

1
2
3
4
5
6
type BaseType = string | number | boolean

// 表明参数类型T是在string | number | boolean这几种类型中
function copy<T extends BaseType>(arg: T): T {
return arg
}

映射类型

in关键字

在 JS 中我们可以通过 for…in 遍历出一个 object{} 的所有 key 然后进行一些逻辑处理。在 TS 中是否有类似的功能用于遍历 interface{}, { [ K in P ] : T }

1
2
type Item = { a: string, b: number, c: boolean };
type Copy = { [K in keyof Item]: Item[K] };

keyof Item得到的是a | b | c,那么type Copy = { [K in keyof Item]: Item[K] }的结果就是type Copy = { a: string, b: number, c: boolean }

映射类型也可以有操作符:

1
2
3
4
5
6
7
8
type Coord = {
readonly [K in 'x' | 'y']?: number;
};
// 得到
// type Coord = {
// readonly x?: number;
// readonly y?: number;
// };

但是映射类型也有局限性,如果类型本省就带有操作符,通过映射之后,无法去除属性上已经存在的装饰符;因此,社区又在 TS2.8 又对其进行了完善,可以在上面的装饰符添加 - 或 + 符号:

1
2
3
4
5
6
7
8
9
10
11
12
type CoordOptional = {
x?: number;
readonly y: number;
};
type Coord = {
-readonly [K in keyof CoordOptional] -?: number;
};
// 得到
// type Coord = {
// x: number;
// y: number;
// };

条件类型

联合类型在 extends 时会默认进行如下转换:
(A | B) extends U ? X : Y ----> (A extends U ? X : Y) | (B extends U ? X : Y)
分析一个条件类型:

1
2
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>

拆解分析:

1
2
3
4
5
6
7
8
Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>
--->
'a' extends 'a' | 'e' ? never : 'a' ,
'b' extends 'a' | 'e' ? never : 'b' ,
'c' extends 'a' | 'e' ? never : 'c'

never | 'b' | 'c' --->
'b' | 'c'

TypeScript 内置类型

Partial
将类型 T 的所有属性标记为可选属性

1
2
3
type Partial<T> = {
[P in keyof T]?: T[P];
};

Record<K, T>
逐个遍历,映射 K 的类型为 T, 返回{K1: T, K2: T,…}

1
2
3
type Record<K extends keyof any, T> = {
[P in K]: T;
};

Required
Required 将类型 T 的所有属性标记为必选属性

1
2
3
type Required<T> = {
[P in keyof T]-?: T[P];
};

Pick<T, K>
从 T 中获取属性 K

1
2
3
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

Exclude<T, U>
移除 T 中的 U 属性

1
type Exclude<T, U> = T extends U ? never : T;

Extract<T, U>
Exclude 的反操作,取 T,U 两者的交集属性

1
type Extract<T, U> = T extends U ? T : never;

NonNullable
排除类型 T 的 null | undefined 属性

1
type NonNullable<T> = T extends null | undefined ? never : T;

unknown、never 推导

unknown

1
2
3
4
5
6
7
8
// T是任意类型,除了any
type x = T | unknown ----> unknown

type x = any | unknown ----> any

type x = T & unknown ----> T

type x = keyof unknown ----> never

never

never 类型是 unknown 的子类型

1
2
3
4
// T是任意类型
type x = T | never ----> T

type x = T & never ----> never

TypeScript 中的问号 ? 与感叹号 !

问号 ?

1
2
3
4
5
6
7
8
9
10
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
return new Error().stack.split('\n');

// 我们可以添加?操作符,当stack属性存在时,调用 stack.split。若stack不存在,则返回空
return new Error().stack?.split('\n');

// 以上代码等同以下代码
const err = new Error();
return err.stack && err.stack.split('\n');

感叹号 !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 因为接口B里面name被定义为可空的值,但是实际情况是不为空的,那么我们就可以
// 通过在class里面使用!,重新强调了name这个不为空值
class A implemented B {
name!: string
}

interface B {
name?: string
}

// 以下是强制链式调用

// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');

// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');

参考文档: