抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

TypeScript 基础再次学习

TypeScript 基础知识

1, 安装使用

全局安装

1
npm i -g TypeScript

初始化文件夹配置文件

1
tsc --init

一般使用,直接输入tsc,就自动把 ts 编译成 js

为了方便,可以监听变化,保存就自动编译

1
tsc --watch

2. 基础类型

TypeScript 提供了 JavaScript 中所有的基础数据类型,如:

  • boolean
  • number
  • string
  • array
  • tuple
  • enum
  • any
  • void
  • null and undefined
  • never

ts 允许我们显示标注类型,或者不标也行,可以隐式推断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let isDone: boolean = false;
let decimal: number = 6;
let color: string = 'blue';

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3]; // 用Array对象定义数组

let x: [string, number]; // 这是元组
x = ['hello', 10];

enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;

let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;

function warnUser(): void {
console.log('This is my warning message');
}

let unusable: void = undefined;

let u: undefined = undefined;
let n: null = null;

3. 联合类型

TypeScript 的一大特性是能够明确指定一个值可能的类型范围。

1
2
3
4
let value: number | string;
value = 'hello'; // Ok
value = 100; // Ok
value = true; // Error: boolean is not assignable to type 'string | number'

4. 类型断言

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。主要是因为 ts 的类型检查只能在编译时进行,编译后会抹去类型,变成普通的 js 代码

1
2
3
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length; // 前置尖括号断言
let strLength: number = (someValue as string).length; // as断言,两种都可以

5. 元组和枚举

元组类型允许表现一个已知元素数量和类型的数组,各元素的类型不必相同。

1
2
3
4
let tupleType: [string, number];
tupleType = ['hello', 10];
console.log(tupleType[0].substring(1)); // OK
console.log(tupleType[1].substring(1)); // Error

枚举是一种特殊的类型,它可以包含一组命名的常量。枚举可以是数字枚举,字符串枚举,异构枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Color {
Red,
Green,
Blue,
} // numeric enum
let c: Color = Color.Green;

enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
} // string enum

6. 接口

接口在 TypeScript 中是一个重要的概念,它是对行为的抽象,也是鸭子类型的一种表现。

鸭子类型 - 维基百科

1
2
3
4
5
6
7
8
9
10
interface LabelledValue {
label: string;
}

function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}

let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);

(简单来说就是,如果你定义了一个接口的结构,然后造了个对象有这个接口一模一样的属性,就算是造这个对象的时候没直接用 implement,TypeScript 也会认为这个对象是这个接口的实现,可以传入某个接受这个接口类型的函数当参数,不会报错)

7. 泛型

泛型就是解决类、接口、方法的复用性,以及对不特定数据类型的支持。

1
2
3
4
5
function identity<T>(arg: T): T {
return arg;
}

let output = identity<string>('myString'); // type of output will be 'string's

8. 装饰器与反射元数据

8.1 装饰器

装饰器,顾名思义,可以添加一些附加功能在代码中。在 TypeScript 中,装饰器提供了一种方法,能在运行时修改类的行为或对类进行额外的处理。它们可以应用在类,方法,属性或者参数等多个地方。这是一个很强大的功能,允许对类的行为进行扩展,而无需改变类的定义。

装饰器有四种类型:

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}

在这个例子中,@sealed装饰器会在运行时应用到Greeter类上,导致无法向Greeter类添加新的属性,并且已有的属性无法删除或配置。

8.2 反射元数据

反射元数据是一种在设计阶段添加和读取元数据的方式。反射 API 包含很多功能,如查询类,接口,参数和返回类型等的设计类型。这为库作者提供了一种检查类结构的强大工具。

在 TypeScript 中使用反射元数据,需要使用reflect-metadata库,并在 tsconfig.json 文件中开启emitDecoratorMetadata选项。

这是一个基础的反射元数据使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'reflect-metadata';

class MyClass {
@validate
public someMethod(someArg: any) {
// Do something
}
}

function validate(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) {
let originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
let metadataValue = Reflect.getMetadata('design:paramtypes', target, propertyKey);
if (metadataValue !== someArg) {
throw new Error(`Validation failed on argument ${someArg}`);
}
return originalMethod.apply(this, args);
};
return descriptor;
}

在这个例子中,validate装饰器使用反射 API 来获取someMethod方法参数的设计类型,并检查是否和方法被调用时传入的参数类型一致。如果不一致,就抛出一个错误。

练习

1. 封装 fetch,实现简单 axios 功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 4种请求方法
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
// 请求配置
interface RequestConfig {
url: string;
method?: Method;
body?: Object;
headers?: Record<string, string>;
}
// 类似axios的返回值设置
interface AxiosResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: RequestConfig;
// 去看了看axios文档,用fetch的话,搞不到axios底层XHR信息,空着算了
request: {};
}

class MyAxios {
// 添加请求和响应拦截器数组
requestInterceptors: Array<(config: RequestConfig) => RequestConfig> = [];
responseInterceptors: Array<(response: AxiosResponse<any>) => AxiosResponse<any>> = [];

// 在发送请求前可以添加请求拦截器
useRequestInterceptor(interceptor: (config: RequestConfig) => RequestConfig): void {
this.requestInterceptors.push(interceptor);
}

// 在收到响应后可以添加响应拦截器
useResponseInterceptor(interceptor: (response: AxiosResponse<any>) => AxiosResponse<any>): void {
this.responseInterceptors.push(interceptor);
}

// fetch请求调用
private async request<T = any>(config: RequestConfig, method: Method): Promise<AxiosResponse<T>> {
// 请求拦截器
this.requestInterceptors.forEach((interceptor) => {
config = interceptor(config);
});

const res: Response = await fetch(config.url, {
method,
body: config.body ? JSON.stringify(config.body) : undefined,
headers: {
'Content-Type': 'application/json',
...config.headers,
},
});

// 处理fetch的返回值让它更像axios的返回值
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}`);
}
const data: T = await res.json();
// status和statusText一样,就不用单独处理,后面直接赋值
const headers: Record<string, string> = {};
res.headers.forEach((value: string, key: string) => {
headers[key] = value;
});
let axiosResponse: AxiosResponse<T> = {
data,
status: res.status,
statusText: res.statusText,
headers,
config,
// 只能空着了
request: {},
};

// 响应拦截器
this.responseInterceptors.forEach((interceptor) => {
axiosResponse = interceptor(axiosResponse);
});

return axiosResponse;
}

// get请求,由于可以传config或者直接传url,所以重载了一下
async get<T = any>(url: string): Promise<AxiosResponse<T>>;
async get<T = any>(config: RequestConfig): Promise<AxiosResponse<T>>;
async get<T = any>(url_or_config: string | RequestConfig): Promise<AxiosResponse<T>> {
let config: RequestConfig;
if (typeof url_or_config === 'string') {
config = { url: url_or_config };
} else {
config = url_or_config;
}
return await this.request<T>(config, 'GET');
}

// post请求
async post<T = any>(config: RequestConfig): Promise<AxiosResponse<T>> {
return await this.request<T>(config, 'POST');
}

// put请求
async put<T = any>(config: RequestConfig): Promise<AxiosResponse<T>> {
return await this.request<T>(config, 'PUT');
}

// delete请求
async delete<T = any>(config: RequestConfig): Promise<AxiosResponse<T>> {
return await this.request<T>(config, 'DELETE');
}
}

const myAxios: MyAxios = new MyAxios();

// 测试get
// 添加请求拦截器,添加统一的请求头
myAxios.useRequestInterceptor((config: RequestConfig) => {
config.headers = {
...config.headers,
'X-My-Custom-Header': 'CustomHeaderValue',
};
return config;
});

// 添加响应拦截器,对响应数据进行一些处理
myAxios.useResponseInterceptor((response: AxiosResponse<any>) => {
// 对响应数据进行处理,这里只是简单的加了一个字段
response.data = {
...response.data,
fromInterceptor: '来自响应拦截器,这是ツユ的歌曲列表:',
};
return response;
});

// 发送请求,查看拦截器是否生效
myAxios.get('http://162.14.111.196:4000/artist/songs?id=34505358').then((res: AxiosResponse) => {
console.log('测试get\n');

console.log(res);
console.log(res.data.fromInterceptor + '\n');

res.data.songs.forEach((song: any) => {
console.log(song.al.name);
if (song.tns) {
console.log(song.tns[0]);
}
console.log();
});
});

2. 类型体操之 DeepReadonly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type X = {
x: {
a: 1;
b: 'hi';
c: {
d: true;
e: 'false';
f: 3;
};
};
y: 'hey';
};

type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type Todo = DeepReadonly<X>;

评论