在 vue3 项目中配置 axios 拦截器,向用户抛出错误消息。主要技术点:
- 使用 axios 进行请求和响应的拦截
- 使用 naive-ui 的 MessageApi 向用户抛出异常
- 使用 TypeScript 类型友好的异步请求
配置 axios 拦截器
import axios from 'axios'
const instance = axios.create({
baseURL: '/api',
timeout: 10000,
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
// },
})
instance.interceptors.request.use(
config => config,
error => {
window.$message.error('响应超时!')
return Promise.resolve(error)
}
)
/** 响应状态码包含两部分:请求状态和数据状态
* 请求正常时 response.status = 200, 使用标准的 HTTP 状态码
* 数据正常时 response.data.code = 0, 使用自定义的业务状态码
*/
instance.interceptors.response.use(
response => {
const { status, data } = response
// 响应状态码不为 200 说明请求异常,交由异常回调处理错误
if (status !== 200) {
return Promise.reject(response)
}
// 响应对象中没有业务状态码时,说明返回的不是 JSON 数据
if (data.code === undefined) {
return Promise.resolve(data)
}
const { code, message } = data
// 业务状态码不为 0 时,说明业务数据出现异常,先打印异常
if (code !== 0) {
window.$message.error(message)
}
// 正常返回响应体中的 JSON 数据
return Promise.resolve(data)
},
// 处理 HTTP code ≠ 200 的响应,通常是请求错误或者服务器错误
error => {
const { status } = error.response
switch (status) {
case 403:
window.$message.error('权限不足,请联系管理员!')
break
case 404:
case 504:
window.$message.error('服务器被吃了⊙﹏⊙∥')
break
default:
window.$message.error('未知错误!')
}
return Promise.resolve()
}
)
export default instance
知识点:
- axios 配置所有请求 api 的 url 头是
/api
,例如/api/users
,vite 中需要配置代理,将以/api
开头的请求转发给后端 - 使用
window.$message.error
方法打印异常消息,这个将在后面进行配置 - 响应拦截的异常检查主要分为以下几步:
- 检查响应状态码
response.status
:不等于 200 视为异常请求,交由异常回调处理 - 正常请求下,检查业务状态码
response.data.code
- 没有定义业务状态码:不是 JSON 格式的响应,可能是文件之类的
- 业务状态码等于 0:正常取回数据,返回带业务状态码的数据对象
response.data
,业务数据保存在response.data.data
中 - 业务状态码不为 0:取回数据异常,使用
window.$message.error
方法打印异常消息 - 没有业务状态码时返回响应体,有业务状态码时返回带业务状态码的数据对象
- 如果响应状态码不等于 200 表示请求异常,由异常回调处理异常,不返回任何数据
- 无效的请求
- 服务器未响应
- ……
- 检查响应状态码
axios 的类型推导
AxiosRequestConfig
用来配置请求数据,泛型D
代表请求体的类型,像 GET 请求就没有这个数据。
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method;
baseURL?: string;
headers?: AxiosRequestHeaders;
params?: any;
data?: D;
//...
}
AxiosResponse
是表示响应内容的同步接口,它带有两个泛型:
T
: Response body dataD
: Request body data
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
axios 提供的请求方法如下:
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
delete<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
head<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
options<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
put<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patch<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
每个方法附带三个泛型:
T
: response bodyR
: axios response, one of whose members is response bodyD
: request body
所以,指定了 axios 请求方法的第一个泛型参数,我们就能友好的使用请求结果的类型了。
在实际应用中,我并不直接指定 axios 请求方法的响应体类型,而是直接指定方法返回值的类型,看起来美观一点。
在此之前让我们先声明两个接口用来简化请求方法返回值类型声明的结构:
declare global {
// data for not jsonic response
type RRD<T = string> = Promise<T>
// data for jsonic response
type JRD<T = any> = Promise<{
code: number
message: string
data?: T
}>
}
export {}
上面的代码片段放到 src/typings 下的随便哪个文件都可以:
RRD
表示 raw response dataJRD
表示 JSON response data
然后我们定义 api 的返回值类型:
import request from './request'
type User = {
name: string
age: number
}
const testService = {
getDemoJson: () => request.get('/demo-json') as JRD<User>,
getDemoFile: () => request.get('/demo-file') as RRD,
getDemoErr: () => request.get('/demo-err'),
}
export default testService
然后就能愉快的使用类型推断了
const { data, loading } = useRequest(testService.getDemoJson)
console.log(data.value?.data?.name)
配置全局消息组件
axios 响应拦截时需要使用 UI 组件向用户打印异常消息,naive-ui 的使用方法要比 element-ui 复杂亿点点。
首先我们新建一个用于全局提示的组件:
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
setup() {
window.$message = useMessage()
},
})
</script>
在上述组件中,我们将消息提示相关的 api 挂载到了window.$message
全局对象上。
要使用 UI 组件还需要在根组件上添加 NMessageProvider 节点:
<template>
<n-message-provider>
<MessageApi />
</n-message-provider>
<router-view />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({})
</script>
至此,window.$message
已经能够使用了,其成员如下:
export interface MessageApiInjection {
info: (content: ContentType, options?: MessageOptions) => MessageReactive;
success: (content: ContentType, options?: MessageOptions) => MessageReactive;
warning: (content: ContentType, options?: MessageOptions) => MessageReactive;
error: (content: ContentType, options?: MessageOptions) => MessageReactive;
loading: (content: ContentType, options?: MessageOptions) => MessageReactive;
destroyAll: () => void;
}
但是,在 ts 中,window
默认并没有$message
这个属性,所以我们可以利用同名 interface 自动合并的特性手动声明这个属性:
import { MessageApi } from 'naive-ui'
declare global {
interface Window {
$message: MessageApi
}
}
export {}
至此,就可以在 TypeScript 中愉快的请求数据了。
评论区