TypeScript语法扩展以及axios封装
1.TypeScript模块使用
TypeScript中最主要使用的模块化方案就是ES Module
1.1非模块
如果你有一个文件,现在没有任何import或者export,但是你希望它被作为模块处理,添加这行代码:export {}
,把文件改成一个没有导出任何内容的模块,解决命名冲突
1.2内置类型导入
使用type前缀,表明被导入的是一个类型,让一个非TypeScript编译器比如Babel,swc或者esbuild知道什么样的导入可以被安全移除
type.ts
export type IDType = number | string
export interface IPerson{
name:string
age:number
}
index.ts
// 导入的是类型,推荐在类型的前面加上type关键
// 作用:只有代码编写时做类型检测需要,代码编译后不需要可以移除掉
// import { type IDType, type IPerson } from "./utils/type";
import type { IDType, IPerson } from "./utils/type";
const id: IDType = 111
const p: IPerson = { name: "jack", age: 18 }
2.TypeScript命名空间(了解)
TypeScript有它自己的模块格式,名为namespaces,它在ES模块标准之前出现
- 命名空间在TypeScript早期时,称之为内部模块,目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题;
- 虽然命名空间没有被废弃,但是由于ES模块已经拥有了命名空间的大部分特性,因此更推荐使用ES模块,这样才能与JavaScript的(发展)方向保持一致。
export namespace price {
export function format(price: string) {
return "¥" + price
}
export const name = "price"
}
export namespace date {
export function format(dateString) {
return "2022-10-10"
}
const name = "date"
}
// 使用命名空间中的内容
import { price, date } from "./utils/format";
price.format("1111")
date.format("22222")
3.内置声明文件的使用
const imageEl = document.getElementById("image") as HTMLImageElement;
HTMLImageElement类型涉及到typescript对类型的管理和查找规则
另外的一种文件.d.ts 文件,它是用来做类型的声明(declare),称之为**类型声明(Type Declaration)或者类型定义(TypeDefinition)**文件,仅仅用来做类型检测,告知typescript我们有哪些类型;
举个例子:
前面编写的type.ts文件重命名为type.d.ts去掉export用来类型声明(不包含业务逻辑代码),并且变为全局文件
那么typescript会在哪里查找我们的类型声明呢?
- 内置类型声明; VScode内置好的
- 外部定义类型声明; 一般axios / React的第三方库
- 自己定义类型声明; 自己编写的
.d.ts
类型声明文件
3.1 内置类型声明
内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;
- 包括比如Function、String、Math、Date等内置类型;
- 也包括运行环境中的DOM API,比如Window、Document等;
TypeScript使用模式命名这些声明文件lib.[something].d.ts
。
内置类型声明通常在我们安装typescript的环境中会带有的:link
3.2 搭建webpack环境,将ts代码运行到浏览器上
- 进入需要操作的目录,打开集成终端执行
npm init
,初始化package.json文件 - 新建src文件夹并在src文件夹里新建index.ts文件,
- 在集成终端执行
npm install webpack webpack-cli -D
,在开发环境(devDependencies)安装webpack - 需要操作的目录新建
webpack.config.js
webpack配置文件,终端执行npm install ts-loader -D
,npm install html-webpack-plugin -D
npm install webpack-dev-server -D
安装webpack服务,开启本地服务 ,webpack.config.js文件会用到ts编译和html模板和webpack-dev-server本地服务,当前目录新建index.html
模板
webpack.config.js:
const path = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: "development",
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "bundle.js"
},
resolve: {
//默认有".js",".cjs",".json",新加的.ts防止覆盖都写
extensions: [".ts", ".js", ".cjs", ".json"]
},
devServer: {
//默认为true
hot: true,
//指定端口
port: 8888,
// host:"0.0.0.0",
//浏览器自动打开
open: true
},
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader"
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
})
]
}
- package.json文件里添加
"scripts": {"serve": "webpack serve" }
,ts-loader需要依赖tsconfig.json文件,终端执行tsc --init
生成tsconfig.json文件,终端执行npm run serve
运行 - 搭建完webpack环境后,编写代码运行ts代码:src目录新建utils文件夹,里面新建math.ts文件export sum函数,index.ts使用
console.log(sum(10,40))
3.3 内置声明的环境
我们可以通过target和lib来决定哪些内置类型声明是可以使用的:
- 例如,startsWith字符串方法只能从称为ECMAScript 6的 JavaScript 版本开始使用;
tsconfig.json
"target": "es2016", 代表ES7
// "lib": [“DOM”],
一般不用tsc编译,babel会根据.browserslistrc文件里面的内容做哪些转化
4.第三方库声明的文件
第三方包如果没有对应类型声明文件应该如何进行处理:
- 包本身包含声明文件 axios
- 没有包含,但在官方DefineTypes库中包含声明文件
npm i @types/react -D - 自己没有声明,DefineTypes库也没有
手写自定义声明文件
5.编写自定义声明文件
什么情况下需要自己来定义声明文件呢?
情况一︰我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件; 比如lodash
情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;
// 需要编写类型声明 index.ts
console.log(whyName, whyAge, whyHeight)
console.log(foo("why"))
const p = new Person("kobe", 30)
console.log(p.name, p.age)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TSDemo</title>
</head>
<body>
<script>
const whyName = "why"
const whyAge = 18
const whyHeight = 1.88
function foo(bar) {
return "hello world"
}
function Person(name, age) {
this.name = name
this.age = age
}
</script>
</body>
</html>
types->why.d.ts
// 为自己的 变量/函数/类 定义类型声明
declare const whyName: string
declare const whyAge: number
declare const whyHeight: number
declare function foo(bar: string): string
declare class Person {
constructor(public name: string, public age: number)
}
5.1 declare声明模块:
我们也可以声明模块,比如lodash模块没有安装npm install @types/lodash -D
的情况,可以自己来声明这个模块:
types文件夹why.d.ts文件
declare module "lodash" {
export function join(...args: any[]): any
}
导入并使用:import _ from “loadsh” _.join([“a”,“b”])
声明模块语法:declare module '模块名' {}
- 在声明模块的内部,我们可以通过export 导出对应库的类、函数等;
5.2 declare 声明文件
在某些情况下,我们也可以声明文件:
- 比如在开发vue的过程中,默认是不识别我们的.vue文件的,那么需要对其进行文件的声明;
- 比如在开发中我们使用了jpg这类图片文件,默认typescript也是不支持的,也需要对其进行声明;
webpack.config.js:
webpack配置加载图片连接链接.
// index.ts 图片文件的使用
import KobeImage from "./img/kobe02.png"//默认ts文件不识别,声明文件模块后才识别
const imgEl = document.createElement("img")
imgEl.src = KobeImage
document.body.append(imgEl)
// types文件夹里的why.d.ts文件
// 声明文件模块
declare module "*.png"
declare module "*.jpg"
declare module "*.jpeg"
declare module "*.svg"
5.3 declare 命名空间
比如我们在index.html中直接引入了jQuery:
CDN地址: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
我们可以进行命名空间的声明:
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
在index.ts中就可以使用了:
// jquery
$.ajax({
url: "http://codercba.com:8000/home/multidata",
success: function (res: any) {
console.log(res)
}
})
6.tsconfig配置文件解析
6.1 tsconfig.json文件作用:
作用一(主要的作用)︰让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测;
- 比如是否允许不明确的this选项,是否允许隐式的any类型;
- 将TypeScript代码编译成什么版本的JavaScript代码;
作用二:让编辑器(比如VSCode)可以按照正确的方式识别TypeScript代码;
- 对于哪些语法进行提示、类型错误检测等等;
JavaScript项目可以使用jsconfig,json文件,它的作用与tsconfig,json 基本相同,只是默认启用了一些JavaScript相关的编译选项。
6.2 tsconfig.json文件配置
- tsconfig.json在编译时如何被使用呢?
- 在调用tsc命令并且没有其它输入文件参数时,编译器将由当前目录开始向父级目录寻找包含tsconfig 文件的目录。
- 调用tsc命令并且没有其他输入文件参数,可以使用–project(或者只是-p)的命令行选项来指定包含了tsconfig.json的目录;
- 当命令行中指定了输入文件参数, tsconfig.json文件会被忽略;
- webpack中使用ts-loader进行打包时,也会自动读取tsconfig文件,根据配置编译TypeScript代码。
- tsconfig.json文件包括哪些选项呢?
- tsconfig.json本身包括的选项非常非常多,我们不需要每一个都记住;
- 可以查看文档对于每个选项的解释: tsconfig.json选项解释链接
- 当我们开发项目的时候,选择TypeScript模板时,tsconfig文件默认都会帮助我们配置好的;
- 接下来我们学习一下哪些重要的、常见的选项。
tsconfig.json顶层选项
{
"compilerOptions": {
},
// "files": [] 指定哪些ts文件需要进行编译 当项目中文件比较少时,可以使用这个选项
// "include": ["./src/**/*.ts", "./types/*.d.ts"] 编写一个数组,用于指定在项目中包括哪些文件默认匹配的是根目录下所有的文件
// "exclude": [] //编写一个数组,用于指定从include中排除哪些文件
}
常见的选项:vuecli生成的一些配置选项
{
"compileroptions":{
//目标代码esnext和ES版本保持一致,最终是靠babel来进行转换,.browserslistrc
"target": "esnext",
//最终生成代码使用的模块化,babel来进行转换
"module": "esnext",
//ts中进行严格的类型检测
"strict": true,
"allowJs": false,//不允许编写js代码
"noImplicitAny": false,//不允许模糊隐式的any
//jsx的处理方式(保留原有的jsx格式),留给babel
"jsx": "preserve",
//是否帮助导入一些需要的功能模块
"importHelpers": true,
//按照node的模块解析规则
//https://www.typescriptlang.org/docs/handbook/module-resolution.html#module-resolution-strategies
"moduleResolution": "node",
//跳过对整个库进行类似检测,·而仅仅检测你用到的类型
"skipLibCheck": true,
//可以让es module·和·commonjs相互调用
"esModuleInterop": true,
// 允许合成默认模块导出
// import * as react from 'react': false
// import react from 'react': true
"allowSyntheticDefaultImports": true,
//是否要生成sourcemap文件
"sourceMap": true,
//文件路径在解析时的基本url
"baseUrl":".",
//指定types文件需要加载哪些(默认是都会进行加载的)
// "types": [
// "webpack-env"
// ],
//路径的映射设置,类似于webpack中的alias
"paths": {
"@/*":["src/*"]
},
// 指定我们需要使用到的库(也可以不配置,直接根据target来获取)
"lib" : ["esnext" , "dom" , "dom.iterable", "scripthost"],
},
"include" : […],
"exclude" : ["node_modules"]
}
7 TS中axios封装
7.1 HYRequest类的基本封装
src\service\request\index.ts
import axios from "axios"
import type {AxiosInstance,AxiosRequestConfig} from "axios"
class HYRequest{
instance:AxiosInstance
// request实例 对应axios的实例
constructor(config:AxiosRequestConfig){
this.instance = axios.create(config)
}
// 封装网络请求的方法
request(config:AxiosRequestConfig){
return this.instance.request(config)
}
get(){
}
post(){
}
}
export default HYRequest
src\service\modules\home.ts调用封装的网络请求request方法
import hyRequest from "../index";
//发送网络请求
// hyRequest.post
hyRequest.request({
url:"/home/multidata"
}).then(res=>{
console.log(res.data);
})
src\service\index.ts
import { BASE_URL, TIME_OUT } from "./config";
import HYRequest from "./request";
//可以根据不同url创建很多实例
// const hyRequest1 = new HYRequest()
// const hyRequest2 = new HYRequest()
const hyRequest = new HYRequest({
baseURL:BASE_URL,
timeout:TIME_OUT
})
export default hyRequest
src\service\config\index.ts
export const BASE_URL = "http://codercba.com:8000"
export const TIME_OUT = 10000
7.2 为全局或者单独的请求(请求爱彼迎接口)添加拦截器的封装
npm install axios@next
加上next装的是:axios@1.2.0-alpha.1版本,axios1.2.4后的版本会报错
import type { HYRequestConfig } from "./type"
constructor(config: HYRequestConfig) {
//每个instance实例都添加拦截器
this.instance.interceptors.request.use((config) => {
/* config.headers = {
token:"xxxx"
} */
// token/loading
console.log("全局请求成功的拦截");
return config
}, (error) => {
// Do something with request error
console.log("全局请求失败的拦截");
return Promise.reject(error);
})
this.instance.interceptors.response.use(res => {
console.log("全局响应成功的拦截");
return res
}, error => {
console.log("全局响应失败的拦截");
return Promise.reject(error);
})
// 针对特定的hyRequest实例添加拦截器
if (config.interceptors) { //类型缩小
this.instance.interceptors.request.use(
config.interceptors.requestSuccessFn,
config.interceptors.requestFailureFn
)
this.instance.interceptors.response.use(
config.interceptors.responseSuccessFn,
config.interceptors.responseFailureFn
)
}
}
import type { AxiosRequestConfig, AxiosResponse } from "axios"
// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: AxiosResponse) => AxiosResponse
responseFailureFn?: (err: any) => any
}
export interface HYRequestConfig extends AxiosRequestConfig {
interceptors?: HYInterceptors
}
import { BASE_URL, TIME_OUT } from "./config";
import HYRequest from "./request";
//可以根据不同url创建很多实例
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT
})
// 针对爱彼迎的接口进行拦截,其他的不拦截
export const hyRequest2 = new HYRequest({
baseURL: "http://codercba.com:1888/airbnb/api",
timeout: 8000,
interceptors: {
requestSuccessFn: (config) => {
console.log("爱彼迎的请求成功的拦截")
return config
},
requestFailureFn: (err) => {
console.log("爱彼迎的请求失败的拦截")
return err
},
responseSuccessFn: (res) => {
console.log("爱彼迎的响应成功的拦截")
return res
},
responseFailureFn: (err) => {
console.log("爱彼迎的响应失败的拦截")
return err
}
}
})
export default hyRequest
7.3 HYRequest类的单次请求拦截
针对不同需求进行不同拦截:
①针对单次请求进行拦截
②实例进行拦截
③全局进行拦截
共三种拦截器
hyRequest2.request({
url:"/home/highscore",
interceptors:{
requestSuccessFn:(config)=>{
console.log("/home/highscore请求成功的拦截");
return config
},
responseSuccessFn:(res)=>{
console.log("/home/highscore响应成功的拦截");
return res
}
}
}).then(res=>{
console.log(res);
})
this.instance.interceptors.response.use(res => {
return res.data
}, error => {
})
// 封装网络请求的方法
request(config: HYRequestConfig) {
// 单次请求的成功拦截处理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return new Promise((resolve, reject) => {
this.instance.request(config).then(res => {
// 单次响应的成功拦截处理
if (config.interceptors?.responseSuccessFn) {
res = config.interceptors.responseSuccessFn(res)
}
resolve(res)
}).catch(err => {
reject(err)
})
})
}
7.4 返回结果的类型处理unknown问题<泛型>
// 封装网络请求的方法
request<T=any>(config: HYRequestConfig) {
// 单次请求的成功拦截处理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return new Promise<T>((resolve, reject) => {
this.instance.request<any, T>(config).then(res => {
// 单次响应的成功拦截处理
if (config.interceptors?.responseSuccessFn) {
// res = config.interceptors.responseSuccessFn(res)
}
resolve(res)
}).catch(err => {
reject(err)
})
})
}
interface IHomeData {
data: any,
returnCode: string,
success: boolean
}
hyRequest.request<IHomeData>({
url: "/home/multidata"
}).then(res => {
console.log(res.data, res.success, res.returnCode);
})
/*
res是unknown类型在上面进行任意的操作都是非法的
new Promise<string>((resolve,reject)=>{
resolve("hahah")
}).then(res=>{
console.log(res.length);
})
*/
interface IHighScoreData {
list: any[],
subtitle: string,
title: string
type: string,
_id: string
}
hyRequest2.request<IHighScoreData>({
url: "/home/highscore",
}).then(res => {
console.log(res.list, res.subtitle, res.title);
})
7.5 接口类型中的泛型处理
import type { AxiosRequestConfig, AxiosResponse } from "axios"
// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (err: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYInterceptors<T>
}
request<T=any>(config: HYRequestConfig<T>) {
// 返回Promise
return new Promise<T>((resolve, reject) => {
this.instance.request<any, T>(config).then(res => {
// 单次响应的成功拦截处理
if (config.interceptors?.responseSuccessFn) {
res = config.interceptors.responseSuccessFn(res)
}
resolve(res)
}).catch(err => {
reject(err)
})
})
}
7.6 其他请求方式的添加
get<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "GET" })
}
post<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "POST" })
}
delete<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "DELETE" })
}
patch<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "PATCH" })
}
7.7 axios封装总结
两个难点:
1.拦截器进行精细控制
-
全局拦截器
-
实例拦截器
-
单次请求拦截
2.响应结果的类型处理(泛型)
写在最后
本篇文章到这里就结束了,如果文章对你有用,可以三连支持一下,如果文章中有错误或者说你有更好的见解,欢迎指正~
代码地址: lemonDin/learn_typescript(github.com).
PS:代码和思路来自王红元(coderwhy)老师,在其中做个总结