本文整理来自深入Vue3+TypeScript技术栈-coderwhy大神新课,只作为个人笔记记录使用,请大家多支持王红元老师。
认识axios
为什么选择axios? 因为作者推荐。
功能特点:
- 在浏览器中发送 XMLHttpRequests 请求
- 在 node.js 中发送 http请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 等等
补充:axios名称的由来?
个人理解,没有具体的翻译,axios:ajax i/o system.
axios请求方式
支持多种请求方式:
- axios(config)
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
import axios from 'axios'
// axios的实例对象
// get请求
axios.get('http://123.207.32.32:8000/home/multidata').then((res) => {
console.log(res.data)
})
// 额外补充的Promise中类型的使用
// Promise本身是可以有类型
new Promise((resolve) => {
// 泛型指定了,只能传string
resolve('abc')
}).then((res) => {
// 并且res也是string类型的
console.log(res.length)
}
// 使用http://httpbin.org模拟数据请求
// get请求,并且传入参数
axios
.get('http://httpbin.org/get', {
// get请求使用params传参,并且最后会拼接到url后面
params: {
name: 'coderwhy',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
// post请求,传入参数
axios
.post('http://httpbin.org/post', {
// post请求使用data传参
data: {
name: 'why',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
有时候,我们可能需求同时发送两个请求,使用axios.all, 可以放入多个请求的数组,当所有请求完成之后,axios.all([]) 会返回一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2。
// axios.all -> 多个请求, 一起返回
axios
.all([
axios.get('/get', { params: { name: 'why', age: 18 } }),
axios.post('/post', { data: { name: 'why', age: 18 } })
])
.then((res) => {
// 结果是个数组
console.log(res[0].data)
console.log(res[1].data)
})
axios常见的配置选项
有时候我们需要配置请求的baseURL和timeout,具体其他配置如下:
解释 | 配置选项 |
---|---|
请求地址 | url: '/user' |
请求类型 | method: 'get' |
请求根路径 | baseURL: 'http://www.mt.com/api' |
请求前的数据处理 | transformRequest:[function(data){}] |
请求后的数据处理 | transformResponse: [function(data){}] |
自定义的请求头 | headers:{'x-Requested-With':'XMLHttpRequest'} |
URL查询对象 | params:{ id: 12 } |
查询对象序列化函数 | paramsSerializer: function(params){ } |
request body | data: { key: 'aa'} |
超时设置 | timeout: 1000 |
跨域是否带Token | withCredentials: false |
自定义请求处理 | adapter: function(resolve, reject, config){} |
身份验证信息 | auth: { uname: '', pwd: '12'} |
响应的数据格式(json、blob、document、arraybuffer、text、stream) | responseType: 'json' |
// axios的配置选项
// 全局的配置 baseURL timeout headers
axios.defaults.baseURL = 'http://httpbin.org'
axios.defaults.timeout = 10000
// axios.defaults.headers = {}
// 每一个请求单独的配置 timeout headers
axios
.get('/get', {
params: {
name: 'coderwhy',
age: 18
},
// 单独配置
timeout: 5000,
headers: {}
})
.then((res) => {
console.log(res.data)
})
// post请求
axios
// 全局配置baseURL之后就不用再写url了
.post('/post', {
data: {
name: 'why',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
axios的实例和拦截器
为什么要创建axios的实例呢?
当我们从axios模块中导入对象时,使用的实例是默认的实例,当给该实例设置一些默认配置时,这些配置就被固定下来了。但是后续开发中,某些配置可能会不太一样,比如某些请求需要使用特定的baseURL或者timeout或者content-Type等,这个时候, 我们就可以创建新的实例,并且传入属于该实例的配置信息。
axios也可以设置拦截器,拦截每次请求和响应:
- axios.interceptors.request.use(请求成功拦截, 请求失败拦截)
- axios.interceptors.response.use(响应成功拦截, 响应失败拦截)
// axios的拦截器
// 参数fn1: 请求发送成功会执行的函数
// 参数fn2: 请求发送失败会执行的函数
axios.interceptors.request.use(
(config) => {
// 想做的一些操作
// 1.给请求添加token
// 2.添加isLoading动画
console.log('请求成功的拦截')
return config
},
(err) => {
console.log('请求发送错误')
return err
}
)
// fn1: 数据响应成功(服务器正常的返回了数据 200)
// fn2: 数据响应失败
axios.interceptors.response.use(
(res) => {
console.log('响应成功的拦截')
return res
},
(err) => {
console.log('服务器响应失败')
return err
}
)
区分不同环境
在开发中,有时候我们需要根据不同的环境设置不同的环境变量,常见的有三种环境:
- 开发环境:development;
- 生产环境:production;
- 测试环境:test;
如何区分环境变量呢?常见有三种方式:
方式一:手动修改不同的变量(不推荐)。
方式二:根据process.env.NODE_ENV的值进行区分,这种方式也是使用很多的一种方式(推荐)。
// 根据process.env.NODE_ENV区分
// 开发环境: development
// 生成环境: production
// 测试环境: test
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
方式三:编写不同的环境变量配置文件,vue cli支持这种方式,我们创建.env.development
、.env.production
、.env.test
文件。
通过环境变量配置文件,我们可以给BASE_URL
、NODE_DEV
设置值,这些值会自动被注入,如果是我们自定义的名字,可以VUE_APP_XXX
这种格式的也可以被注入。
.env.development文件:
VUE_APP_BASE_URL=https://coderwhy.org/dev
VUE_APP_BASE_NAME=coderwhy
.env.production文件:
VUE_APP_BASE_URL=https://coderwhy.org/prod
VUE_APP_BASE_NAME=kobe
.env.test文件:
VUE_APP_BASE_URL=https://coderwhy.org/test
VUE_APP_BASE_NAME=james
在JS中,我们直接使用不会报错,在TS中直接使用会报错:
// 报错
console.log(process.env.VUE_APP_BASE_URL)
我们在shims-vue.d.ts文件中声明一下即可:
declare const VUE_APP_BASE_URL: string
npm run build打包项目,打开打包后的build文件夹下的index.html文件,通过live serve打开index.html文件,这时候很多文件是加载不到的:
加载不到的原因是因为上面的路径是根据域名拼接的绝对路径,我们可以进入index.html中,将加载文件的路径改成相对路径:src="./js/chunk-xxxxxx.js"
,也就是加载当前路径下的js文件夹下的文件,一个一个改路径,比较麻烦。
打包之后,如果不想手动一个一个改路径,可以进入vue.config.js文件中,添加publicPath: './'
,这个值其实就是修改加载资源的路径,但是部署到服务器的时候肯定不需要这个值了,注释掉即可,或者加个环境判断也可以。
封装axios
先讲一下逻辑,我们将其封装成一个对象(为什么封装成类呢?因为使用类封装性更强一点),新建service文件夹,service文件夹里面再新建request文件夹和index.ts文件,然后在main.ts里面引入这个对象,然后就可以使用了,文件目录如下:
首先要安装axios:
npm install axios
先说一下我们封装要达到的目的:可以对某个请求、某个请求实例的所有请求、所有请求实例的所有请求,设置拦截和是否显示loading。
下面就对每个文件的代码以及作用进行讲解:
config.ts代码如下,会根据环境配置不同的BASE_URL。
// 根据process.env.NODE_ENV区分
// 开发环境: development
// 生成环境: production
// 测试环境: test
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
在type.js里面我们定义一个接口,用于规定创建请求实例或者调用request方法的时候传入的参数是什么样的,代码如下:
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
// 定义一个接口,表示这个接口的实例要有这4个属性,当然不是必须的,是可选的
// 传入一个泛型,默认值是AxiosResponse
export interface HYRequestInterceptors {
// 拦截器都是可选的
// 请求拦截
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
// 请求错误拦截
requestInterceptorCatch?: (error: any) => any
// 响应拦截
// 由于我们在前面直接将res.data返回了,所以这里如果传入了T,那么返回的类型就是传入的T
responseInterceptor?: (res: T) => T
// 响应错误拦截
responseInterceptorCatch?: (error: any) => any
}
// 定义一个新的接口,继承于AxiosRequestConfig,表示我们传入的参数要有interceptors和showLoading,当然也是可选的
export interface HYRequestConfig extends AxiosRequestConfig {
// 对原来的AxiosRequestConfig进行扩展,添加拦截器和是否显示loading,可选的
interceptors?: HYRequestInterceptors
showLoading?: boolean
}
核心代码就是request文件夹下的index.js文件,代码如下:
import axios from 'axios'
// 导入axios实例的类型
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'
// 引入loading组件
import { ElLoading } from 'element-plus'
// 引入loading组件的类型
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'
// 默认显示loading
const DEAFULT_LOADING = true
class HYRequest {
// axios实例
instance: AxiosInstance
// 当前请求实例的拦截器
interceptors?: HYRequestInterceptors
// 是否显示loading
showLoading: boolean
// 保存的loading实例
loading?: ILoadingInstance
constructor(config: HYRequestConfig) {
// 创建axios实例
this.instance = axios.create(config)
// 保存基本信息
this.interceptors = config.interceptors
this.showLoading = config.showLoading ?? DEAFULT_LOADING
// 使用拦截器
// 1.从config中取出的拦截器是对应的实例的拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 2.添加所有的实例都有的拦截器
// 请求的时候,先添加的拦截器后执行
// 响应的时候,先添加的拦截器先执行
this.instance.interceptors.request.use(
(config) => {
console.log('所有的实例都有的拦截器: 请求成功拦截')
// 所有的请求都添加loading
if (this.showLoading) {
// 添加loading
this.loading = ElLoading.service({
lock: true,
text: '正在请求数据....',
background: 'rgba(0, 0, 0, 0.5)'
})
}
return config
},
(err) => {
console.log('所有的实例都有的拦截器: 请求失败拦截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('所有的实例都有的拦截器: 响应成功拦截')
// 所有的请求,将loading移除
this.loading?.close()
// 因为我们需要的就是res.data,所以我们可以在所有请求实例的请求的响应拦截器里面,直接把res.data返回,这样我们就可以直接使用了
const data = res.data
// 判断当HttpErrorCode是200的时候,服务端和客户端一块自定义的错误信息
if (data.returnCode === '-1001') {
console.log('请求失败~, 错误信息')
} else {
return data
}
},
(err) => {
console.log('所有的实例都有的拦截器: 响应失败拦截')
// 所有的请求,将loading移除
this.loading?.close()
// 判断不同的HttpErrorCode显示不同的错误信息
if (err.response.status === 404) {
console.log('404的错误~')
}
return err
}
)
}
// 1.传入返回结果的类型T,这样在Promise中我们就知道返回值的类型是T了
// 2.通过HYRequestConfig,将返回值类型T告诉接口,从而在接口的返回响应拦截中指明返回值类型就是T
request(config: HYRequestConfig): Promise {
// 返回一个Promise对象,好让使用者在外面拿到数据
return new Promise((resolve, reject) => {
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
// 如果有单个请求的拦截器,就执行一下这个函数,然后返回
config = config.interceptors.requestInterceptor(config)
}
// 2.判断单个请求是否需要显示loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
// request里面有两个泛型,第一个泛型默认是any,第二个泛型是AxiosResponse
// 由于前面我们已经将res.data直接返回了,所以其实最后的数据就是T类型的,所以我们在第二个泛型中要指定返回值的类型T
.request(config)
.then((res) => {
// 1.单个请求对数据的处理
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 2.将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
// 3.将结果resolve返回出去
resolve(res)
})
.catch((err) => {
// 将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
reject(err)
return err
})
})
}
get(config: HYRequestConfig): Promise {
return this.request({ ...config, method: 'GET' })
}
post(config: HYRequestConfig): Promise {
return this.request({ ...config, method: 'POST' })
}
delete(config: HYRequestConfig): Promise {
return this.request({ ...config, method: 'DELETE' })
}
patch(config: HYRequestConfig): Promise {
return this.request({ ...config, method: 'PATCH' })
}
}
export default HYRequest
这时候我们需要创建一个请求实例,用于发送网络请求,当然我们也可以创建不止一个请求实例,然后设置不同的baseurl、超时时间、拦截器等等,这里我们只创建一个,所以外层的index.ts代码如下:
// service统一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
// 创建一个新的请求,并传入参数
const hyRequest = new HYRequest({
// 传入baseurl
baseURL: BASE_URL,
// 传入超时时间
timeout: TIME_OUT,
// 传入拦截器
interceptors: {
requestInterceptor: (config) => {
// 给当前请求实例所有的请求添加token
const token = ''
if (token) {
// 模板字符串进行拼接
config.headers.Authorization = `Bearer ${token}`
}
console.log('请求成功的拦截')
return config
},
requestInterceptorCatch: (err) => {
console.log('请求失败的拦截')
return err
},
responseInterceptor: (res) => {
console.log('响应成功的拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('响应失败的拦截')
return err
}
}
})
export default hyRequest
在main.ts中使用如下:
import { createApp } from 'vue'
import App from './App.vue'
// 导入请求实例
import hyRequest from './service'
const app = createApp(App)
app.mount('#app')
hyRequest.request({
url: '/home/multidata',
method: 'GET',
headers: {},
interceptors: {
requestInterceptor: (config) => {
console.log('单独请求的config')
config.headers['token'] = '123'
return config
},
responseInterceptor: (res) => {
console.log('单独响应的response')
return res
}
}
})
// 定义返回结果的类型
interface DataType {
data: any
returnCode: string
success: boolean
}
// 只有请求者才知道返回结果的类型
hyRequest
.get({
url: '/home/multidata',
showLoading: false
})
// 这时候这里的res就是DataType类型的
.then((res) => {
console.log(res.data)
console.log(res.returnCode)
console.log(res.success)
})
注意:Vuex和TS的结合比较难用,所以如果使用TS,我们一般使用pinia这个库来代替Vuex。