五、TypeScript知识/语法扩展
Lyk 2022/9/6 TypeScriptTS模块化命名空间namespacedeclare声明了解tsconfig.json文件axios二次封装
# 1、TypeScript模块化
- JavaScript 有一个很长的处理模块化代码的历史,TypeScript 从 2012 年开始跟进,现在已经实现支持了很多格式。但是随着时间流逝,社区和 JavaScript 规范已经使用为名为 ES Module的格式,这也就是我们所知的 import/export 语法。
- ES 模块在 2015 年被添加到 JavaScript 规范中,到 2020 年,大部分的 web 浏览器和 JavaScript 运行环境都已经广泛支持。
- 所以在TypeScript中最主要使用的模块化方案就是ES Module;
export function add(num1: number, num2: number) {
return num1 + num2
}
export function sub(num1:number, num2: number) {
return num1 - num2
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 在前面我们已经学习过各种各样模块化方案以及对应的细节,这里我们主要学习TypeScript中一些比较特别的细节。
# 2、非模块(Non-modules)
- 我们需要先理解 TypeScript 认为什么是一个模块。
- JavaScript 规范声明任何没有 export 的 JavaScript 文件都应该被认为是一个脚本,而非一个模块。
- 在一个脚本文件中,变量和类型会被声明在共享的全局作用域,将多个输入文件合并成一个输出文件,或者在 HTML使用多个
<script>
标签加载这些文件。
- 如果你有一个文件,现在没有任何 import 或者 export,但是你希望它被作为模块处理,添加这行代码:
export {}
- 这会把文件改成一个没有导出任何内容的模块,这个语法可以生效,无论你的模块目标是什么
# 3、内置类型导入(Inline type imports)
- TypeScript 4.5 也允许单独的导入,你需要使用 type 前缀 ,表明被导入的是一个类型:
//foo.ts
export type IDType = number
export type IFoo = {
name: string,
age: number
}
//bar.ts
import { type IFoo, type IDType } from './foo' //这样也可以:import type { IFoo, IDType } from './foo'
const id: IDType = 1032423
const foo: IFoo = {
name: 'kobe',
age: 45
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 这些可以让一个非 TypeScript 编译器比如 Babel、swc 或者 esbuild 知道什么样的导入可以被安全移除。
# 4、命名空间namespace(了解)
- TypeScript 有它自己的模块格式,名为 namespaces ,它在 ES 模块标准之前出现。
- 命名空间在TypeScript早期时,称之为内部模块,目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题;
- 虽然命名空间没有被废弃,但是由于 ES 模块已经拥有了命名空间的大部分特性,因此更推荐使用 ES 模块,这样才能与JavaScript 的(发展)方向保持一致。
export namespace Time {
export function format(time: string) {
return time
}
export const name = 'kobe'
}
export namespace Price {
export function format(price: string) {
return price
}
}
console.log(Time.format('2022-05-10'))//2022-05-10
console.log(Price.format("¥29.99"))//¥29.99
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5、类型的查找
- 之前我们所有的typescript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型:
const imageEl = document.getElementById("image") as HTMLImageElement;
1
- 大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?
- 其实这里就涉及到typescript对类型的管理和查找规则了。
- 我们这里先给大家介绍另外的一种typescript文件:.d.ts文件
- 我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
- 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare),称之为类型声明(Type Declaration)或者类型定义(Type Definition)文件。
- 它仅仅用来做类型检测,告知typescript我们有哪些类型;
- 那么typescript会在哪里查找我们的类型声明呢?
- 内置类型声明;
- 外部定义类型声明;
- 自己定义类型声明;
# 6、内置类型声明
- 内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;
- 包括比如Function、String、Math、Date等内置类型;
- 也包括运行环境中的DOM API,比如Window、Document等;
- TypeScript 使用模式命名这些声明文件lib.[something].d.ts。
- 内置类型声明通常在我们安装typescript的环境中会带有的;
# 7、内置声明的环境
- 我们可以通过target和lib来决定哪些内置类型声明是可以使用的:
- 例如,startsWith字符串方法只能从称为ECMAScript 6的 JavaScript 版本开始使用;
- 我们可以通过target的编译选项来配置:TypeScript通过lib根据您的target设置更改默认包含的文件来帮助解决此问题。
# 8、外部定义类型声明 – 第三方库
- 外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。
- 这些库通常有两种类型声明方式:
- 方式一:在自己库中进行类型声明(编写.d.ts文件),比如axios(axios内部写了自己的类型声明,即axios的依赖包中有后缀为.d.ts的文件)
- 方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件
- DefinitelyTyped库的GitHub地址:DefinitelyTyped库的GitHub地址 (opens new window)
- 查找声明安装方式的地址(如果在DefinitelyTyped库中没有找到,可以到这里查找):查找声明安装方式的地址 (opens new window)
- 比如我们安装react的类型声明:
npm i @types/react --save-dev
- 官方描述:TypeScript automatically finds type definitions under node_modules/@types. so there's no other step needed to get these types available in your program【TypeScript自动在node_modules/@types下查找类型定义。因此,无需其他步骤即可在程序中获得这些类型】
import React from 'react'
import axios from 'axios'
1
2
2
# 9、外部定义类型声明 – 自定义声明
- 什么情况下需要自己来定义声明文件呢?
- 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
- 情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;
- 如下情况,我们在项目的index.html模板中进行了:
- 在引入项目入口文件前,声明了一些变量,函数,类;所以app组件是可以访问到这些变量,函数和类的;这是没有问题的
- 但是该项目我们用的是Vue+TS;我们会发现,ts这里会有报错提示:找不到这些变量,函数,类
- 所以这个时候,我们可以在env.d.ts进行自定义变量类型声明,告诉ts我们有这些变量,函数,类 即可解决,ts报错的问题
- 解决上面问题:
# 10、declare 声明模块
- 我们也可以声明模块,比如lodash模块默认不能使用的情况,可以自己来声明这个模块:
declare module 'lodash' {
export function join(array: any, separator?: string | undefined): string;
}
1
2
3
2
3
- 声明模块的语法: declare module '模块名' {}。
- 在声明模块的内部,我们可以通过 export 导出对应库的类、函数等;
# 11、declare 声明文件
在某些情况下,我们也可以声明文件:
- 比如在开发vue的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明;
- 比如在开发中我们使用了 jpg 这类图片文件,默认typescript也是不支持的,也需要对其进行声明;
declare module '*.vue' {//声明文件模块
import type { defineComponent } from "vue";
const component: defineComponent
export default component
}
// images
declare module '*.png' {
const src: string
export default src
}
declare module '*.jpg' {
const src: string
export default src
}
declare module '*.jpeg' {
const src: string
export default src
}
declare module '*.jfif' {
const src: string
export default src
}
declare module '*.pjpeg' {
const src: string
export default src
}
declare module '*.pjp' {
const src: string
export default src
}
declare module '*.gif' {
const src: string
export default src
}
declare module '*.svg' {
const src: string
export default src
}
declare module '*.ico' {
const src: string
export default src
}
declare module '*.webp' {
const src: string
export default src
}
declare module '*.avif' {
const src: string
export default src
}
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
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
# 12、declare 命名空间
比如我们在index.html中直接引入了jQuery:
- CDN地址: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
我们可以进行命名空间的声明:在main.ts或app组件中就可以使用了:如下案例
未进行命名空间的声明时,出现问题:
- 解决问题:声明命名空间
# 13、认识tsconfig.json文件
什么是tsconfig.json文件呢?(官方的解释)
- 当目录中出现了 tsconfig.json 文件,则说明该目录是 TypeScript 项目的根目录;
- tsconfig.json 文件指定了编译项目所需的根目录下的文件以及编译选项。
官方的解释有点“官方”,直接看我的解释。
tsconfig.json文件有两个作用:
作用一(主要的作用):让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测;
- 比如是否允许不明确的this选项,是否允许隐式的any类型;
- 将TypeScript代码编译成什么版本的JavaScript代码;
作用二:让编辑器(比如VSCode)可以按照正确的方式识别TypeScript代码;
- 对于哪些语法进行提示、类型错误检测等等;
JavaScript 项目可以使用 jsconfig.json 文件,它的作用与 tsconfig.json 基本相同,只是默认启用了一些 JavaScript 相关的编译选项。
- 在之前的Vue项目、React项目中我们也有使用过;
# 14、tsconfig.json配置
- tsconfig.json在编译时如何被使用呢?
- 在调用 tsc 命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含 tsconfig 文件的目录。
- 调用 tsc 命令并且没有其他输入文件参数,可以使用 --project (或者只是 -p)的命令行选项来指定包含了 tsconfig.json 的目录;
- 当命令行中指定了输入文件参数, tsconfig.json 文件会被忽略;
- webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码。
- tsconfig.json文件包括哪些选项呢?
- tsconfig.json本身包括的选项非常非常多,我们不需要每一个都记住;
- 可以查看文档对于每个选项的解释:tsconfig.json配置文件的配置选项学习 (opens new window)
- 当我们开发项目的时候,选择TypeScript模板时,tsconfig文件默认都会帮助我们配置好的;
- 接下来我们学习一下哪些重要的、常见的选项。
# 15、tsconfig.json顶层选项
# 16、tsconfig.json文件
- tsconfig.json是用于配置TypeScript编译时的配置选项:
- 我们这里学习几个比较常见的:
# 17、axios的二次封装(TS版)
- 项目结构
- ./src/service/request/index.ts
import axios from "axios"
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"
import type { useCssModule } from "vue"
interface HYInstanceInterceptors<T = AxiosResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (err: any) => any
responseInterceptor?: (res: T) => T
responseInterceptorCatch?: (err: any) => any
}
interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYInstanceInterceptors<T>
}
class HYRequest<T = AxiosResponse> {
instance: AxiosInstance
constructor(config: HYRequestConfig) {
this.instance = axios.create(config)
// 全局的拦截器
this.instance.interceptors.request.use((config) => {
console.log("全局请求成功拦截")
return config
}, err => {
return err
})
this.instance.interceptors.response.use((res) => {
console.log("全局响应成功拦截")
return res.data
}, err => {
console.log("全局响应失败拦截")
return err
})
// 实例的拦截器
this.instance.interceptors.request.use(
config.interceptors?.requestInterceptor,
config.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
config.interceptors?.responseInterceptor,
config.interceptors?.responseInterceptorCatch
)
}
request<T>(config: HYRequestConfig<T>) {
if (config.interceptors?.requestInterceptor) {
config = config.interceptors.requestInterceptor(config)
}
return new Promise<T>((resolve, reject) => {
this.instance.request<any, T>(config).then(res => {
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
resolve(res)
}).catch((err: any) => {
if (config.interceptors?.responseInterceptorCatch) {
err = config.interceptors.responseInterceptorCatch(err)
}
reject(err)
})
})
}
get<T>(config: HYRequestConfig<T>) {
return this.request<T>({ ...config, method: 'GET' })
}
post<T>(config: HYRequestConfig<T>) {
return this.request<T>({ ...config, method: "POST" })
}
}
export default HYRequest
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
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
- ./src/service/config/index.ts
export const BASE_URL1 = "https://www.fastmock.site/mock/2255035612ef720bb19e75aae3ad1d90/home/api"
export const TIME_OUT1 = 10000
1
2
2
- ./src/service/index.ts
import HYRequest from "./request";
import { BASE_URL1, TIME_OUT1 } from "./config"
const hyRequest = new HYRequest({
baseURL: BASE_URL1,
timeout: TIME_OUT1,
interceptors: {
requestInterceptor: config => {
console.log("实例请求成功拦截")
return config
},
requestInterceptorCatch: err => {
console.log("实例请求失败拦截")
},
responseInterceptor: res => {
console.log("实例响应成功拦截", res)
return res
},
responseInterceptorCatch: err => {
console.log("实例响应失败拦截")
return err
}
}
})
export default hyRequest
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
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
- ./src/App.vue
<template>
<h2>我是App组件</h2>
</template>
<script setup lang="ts">
import hyRequest from "./service"
// axios请求测试
interface IDataType {
data: {
[p: string]: { name: string, price: number }
}
}
hyRequest.get<IDataType>({
url: "/test",
interceptors: {
requestInterceptor: config => {
console.log("单次请求的成功拦截")
return config
},
requestInterceptorCatch: err => {
console.log("单次请求的失败拦截")
return err
},
responseInterceptor: res => {
console.log("单次响应的成功拦截")
return res
},
responseInterceptorCatch: err => {
console.log("单次响应的失败拦截")
return err
}
}
}).then(res => {
console.log(res.data,res.data[0].name,res.data[0].price)
})
// hyRequest.post<IDataType1>({//IDataType1类型这里没有声明,使用时记得声明
// url: "/post_lyk",
// params: {
// username: 'admin',
// password: '123456'
// },
// interceptors: {
// requestInterceptor: config => {
// console.log("单次请求的成功拦截")
// return config
// },
// requestInterceptorCatch: err => {
// console.log("单次请求的失败拦截")
// return err
// },
// responseInterceptor: res => {
// console.log("单次响应的成功拦截")
// return res
// },
// responseInterceptorCatch: err => {
// console.log("单次响应的失败拦截")
// return err
// }
// }
// }).then(res => {
// console.log(res.data, res.returnCode, res.success)
// })
// hyRequest.get<IDataType2>({//IDataType2类型这里没有声明,使用时记得声明
// url: "/user",
// interceptors: {
// requestInterceptor: config => {
// console.log("单次请求的成功拦截")
// return config
// },
// requestInterceptorCatch: err => {
// console.log("单次请求的失败拦截")
// return err
// },
// responseInterceptor: res => {
// console.log("单次响应的成功拦截")
// return res
// },
// responseInterceptorCatch: err => {
// console.log("单次响应的失败拦截")
// return err
// }
// }
// }).then(res => {
// console.log(res.data, res.returnCode, res.success)
// })
</script>
<style scoped>
</style>
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
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