前言
从第一次了解前端到现在也已有两年的时间了,记得上网课的时候老师会说可以多多写一些技术性、总结性的文章帮助自己巩固知识、也可以有更多的机会让别人看见自己、找工作面试的时候可以加分。由于对自己的技术一直不自信、且学的也不精一直没有敢尝试写文章。但人生总要有个拐点,所以我就尝试写下第一篇属于自己的技术分享文章。(封装内容学习来自codewhy老师的Vue3+Typescript系统课)中间的内容如果有错误的地方希望大家可以帮我指正修改
正文
前段时间工作中遇到几个请求的场景
- 数据来自多个服务器,有多个baseUrl
- 某一个请求时间很长需要单独设置timeout。
首先我们需要安装aixos依赖
在项目中创建service文件夹并在里面新建文件进行练习和封装
axios可以不进行封装直接使用,但为了方便维护和代码的高内聚低耦合我们需要对axios进行二次封装
- 直接使用axios
// get 方法里第一个参数是url,可以是完整的url地址,也可以和第二个参数里的baseURl拼接的地址
// 第二个参数传的是AxiosRequestConfig类型包含baseURL、timeout、headers等等
// get方法返回的是Promise类型,所以使用.then可以获取到接口返回的数据
axios
.get('http://lilian.org', {
timeout: 20000,
baseURL: '',
})
.then((res) => {
console.log(res)
})
// get方法其实是对request方法的封装,在使用request方法的时候我们传的参数也有所不一样
axios.request({ method: 'GET', url: 'http://lilian.org' }).then((res) => {
console.log(res)
})
- get方法请求数据传的参数我们可以从提示中看出
- 具体config可以传的参数/以及axios用到的类型可以看axios依赖里的index.d.ts(axios的类型接口文件)
- 具体config可以传的参数/以及axios用到的类型可以看axios依赖里的index.d.ts(axios的类型接口文件)
- request方法请求数据和get不同,只需要传一个类型为AxiosRequestConfig的参数,可以作为任何请求方法来使用,定义methods就可以实现
梳理封装思路
封装到最后我们肯定希望是在使用的时候只需要通过方法的调用传参就可以完成接口的请求,而baseUrl、url、timeout不必每次都作为参数传到方法里,在需要传params的时候传即可
// 就像这样
async function getBanner() {
const result = await banner()
console.log(result)
}
那为了满足有多个baseURL的情景,可以使用类进行封装,包含拦截器、请求方法。创建新的实例后调用实例里封装好的请求方法即可
用类简单实现
// index.ts 使用类来简单实现封装axios
import axios, { type AxiosInstance } from 'axios'
import type { AxiosRequestConfig } from 'axios'
class MyRequest {
instance: AxiosInstance
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
}
request(config: AxiosRequestConfig) {
this.instance.request(config).then((res) => {
console.log(res)
return res
})
}
}
export default MyRequest
// main.ts里调用
import MyRequest from '@/service/request/index'
// 这里可以给每一个实例设置timeout和baseURL
const myRequest = new MyRequest({
baseURL: 'http://123.207.32.32:8000',
timeout: 10000,
})
// 调用方法的时候也可以给每一个方法设置单独的timeout
myRequest.request({
url: '/home/multidata',
})
index.ts里打印的数据返回结果
拦截器封装
- 全局拦截器 全局拦截器在类的构造函数里实现
// 全局拦截器
this.instance.interceptors.request.use(
(config) => {
console.log('请求拦截成功')
return config
},
(err) => {
return err
},
)
this.instance.interceptors.response.use(
(res) => {
console.log('响应拦截成功')
return res
},
(err) => {
return err
},
)
- 实例单独的拦截器 实例的拦截器实现在于创建实例的时候传入拦截器,所以我们需要在传入的config里面添加拦截器,但是现在传入的config是AxiosRequestConfig类型,没有拦截器属性,所以我们需要给config修改类型
- 定义一个拦截器类型然后继承于AxiosRequeatConfig类型
- 实例的拦截器可以定义为可选参数(不一定每个实例都需要拦截器)
// index.ts
import type {
AxiosRequestConfig,
InternalAxiosRequestConfig,
AxiosResponse,
AxiosInstance,
} from 'axios'
interface MyIntercepors {
requestInterceptors?: (
config: InternalAxiosRequestConfig,
) => InternalAxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
responseInterceptors?: (res: AxiosResponse) => AxiosResponse
responseInterceptorsCatch?: (err: any) => any
}
interface MyRequestConfig extends AxiosRequestConfig {
interceptors?: MyIntercepors
}
class MyRequest {
instance: AxiosInstance
interceptors?: MyIntercepors
constructor(config: MyRequestConfig) {
this.instance = axios.create(config)
this.interceptors = config.interceptors
// 单独实例的拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptors,
this.interceptors?.requestInterceptorsCatch,
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptors,
this.interceptors?.responseInterceptorsCatch,
)
// 全局拦截器
this.instance.interceptors.request.use(
(config) => {
console.log('请求拦截成功')
return config
},
(err) => {
return err
},
)
this.instance.interceptors.response.use(
(res) => {
console.log('响应拦截成功')
return res
},
(err) => {
return err
},
)
}
request(config: AxiosRequestConfig) {
this.instance.request(config).then((res) => {
console.log(res)
return res
})
}
}
*单独请求的拦截器在这里就不做封装了
- 使用单独实例拦截器
// main.ts
const myRequest = new MyRequest({
baseURL: 'http://123.207.32.32:8000',
timeout: 10000,
interceptors: {
requestInterceptors: (config) => {
console.log(config.timeout)
return config
},
requestInterceptorsCatch: (err) => {
console.log('请求失败')
return err
},
responseInterceptors: (res) => {
console.log('响应成功')
return res.data
},
responseInterceptorsCatch: (error) => {
console.log('响应失败')
return error
},
},
})
这里我使用单独实例的拦截器,一般看项目需求,如果只有一个baseURL的话可以只写全局拦截器,以前我接手的项目里loading动画是组件化的(因为是数据大屏,样式要求比较高),其实可以在拦截器里编写,我个人觉得如果对loading动画的效果不做高要求可以直接的在拦截器里调用
完善请求方法的封装
- 原本的请求返回的是promise类型数据,所以我们自己封装的时候也应该让返回的数据是promise
// index.ts
request<T>(config: AxiosRequestConfig): Promise<T> {
return new Promise((resolve, rejects) => {
this.instance
.request<any, T>(config)
.then(
(res) => {
console.log(res)
resolve(res)
return res
},
(err) => {
return err
},
)
.catch((err) => {
rejects(err)
return err
})
})
}
get<T>(config: AxiosRequestConfig): Promise<T> {
return this.request({ ...config, method: 'GET' })
}
post<T>(config: AxiosRequestConfig): Promise<T> {
return this.request({ ...config, method: 'POST' })
}
delete<T>(config: AxiosRequestConfig): Promise<T> {
return this.request({ ...config, method: 'DELETE' })
}
- 封装请求方法类型
export interface banner {
banner: any
dKeyword: any
keywords: any
recommend: any
}
export interface DataType<T> {
data: T
returnCode: string
scuccess: boolean
}
export function banner() {
return myRequest.get<DataType<banner>>({
url: '/home/multidata',
timeout: 20000,
})
}
- 调用请求方法
import { banner } from '@/service/login/login'
async function getBanner() {
const result = await banner()
console.log(result)
}
个人觉得这样的axios封装是非常好的,可以控制baseURL、timeout、甚至用interface定义了接口里返回的有哪些字段,大大的减少了我们在使用接口数据时人为编写产生的bug。在封装的过程中,由于时强类型语言,需要关注给定的类型注解这是非常重要的。
最后
第一次编写掘金文章非常生疏,文章也相对简陋,但是编写的过程中让我重新梳理的逻辑和知识点我觉得这是一次非常好的体验,也非常有成就感。迈出这一步也不是那么困难,但我还有一个顾虑就是对于技术严谨的朋友们我蛮害怕在评论区喷我,我是一个技术菜鸟,轻点喷。求求了。