侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

Vue3模板 - 结合 NaiveUI 提供的消息组件配置 Axios

barwe
2022-05-15 / 0 评论 / 2 点赞 / 2,505 阅读 / 5,106 字
温馨提示:
本文最后更新于 2022-05-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

在 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方法打印异常消息,这个将在后面进行配置
  • 响应拦截的异常检查主要分为以下几步:
    1. 检查响应状态码response.status:不等于 200 视为异常请求,交由异常回调处理
    2. 正常请求下,检查业务状态码response.data.code
      1. 没有定义业务状态码:不是 JSON 格式的响应,可能是文件之类的
      2. 业务状态码等于 0:正常取回数据,返回带业务状态码的数据对象response.data,业务数据保存在response.data.data
      3. 业务状态码不为 0:取回数据异常,使用window.$message.error方法打印异常消息
      4. 没有业务状态码时返回响应体,有业务状态码时返回带业务状态码的数据对象
    3. 如果响应状态码不等于 200 表示请求异常,由异常回调处理异常,不返回任何数据
      1. 无效的请求
      2. 服务器未响应
      3. ……

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 data
  • D: 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 body
  • R: axios response, one of whose members is response body
  • D: 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 data
  • JRD表示 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 中愉快的请求数据了。

2

评论区