ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第十章 请求配置 (Axios请求和响应拦截)

前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第十章 请求配置 (Axios请求和响应拦截)

系列文章目录(点击查看)


文章目录

  • 系列文章目录(点击查看)
  • 前言
  • 一、安装和配置
    • 1、安装
    • 2、配置
  • 二、拦截器介绍和使用
    • 1、拦截器介绍
    • 2、拦截器的使用
  • 三、axios 请求封装
    • 1、config.ts文件
    • 2、types.ts文件
    • 3、http.ts文件
    • 4、index.ts文件
    • 5、使用封装
  • 四、mock 的使用
    • 1、什么是 mockjs
    • 2、安装
    • 3、配置
    • 4、界面测试 mock
  • 总结


前言

在Vue项目中使用 axios 是常见的实践,因为 axios 是一个流行的基于Promise 的 HTTP 客户端,可用于浏览器和 Node.js 环境。它提供了许多优点,包括易用性、可靠性和在处理 HTTP 请求时的灵活性。

使用 axios 可以轻松地执行 HTTP 请求,包括 GET、POST 等,还可以简化对响应数据的处理。此外,axios 还提供了拦截器(interceptors)、取消请求、全局配置等功能,使其成为在 Vue 项目中进行 HTTP 通信的强大工具。

axios 相比其他 HTTP 客户端库具有几个优势:

    1. 基于 Promise:axios利用 Promise 对象来处理请求和响应,使得代码更加清晰、可读,并且更容易处理异步操作。
    1. 浏览器和Node.js兼容:axios 可以在浏览器和 Node.js 环境中使用,这使得它非常灵活,可以在各种环境中进行 HTTP 通信。
    1. 易用性:axios 的 API 设计非常直观和简单,使其易于学习和使用。它提供了丰富的配置选项和拦截器接口,以满足各种复杂的 HTTP 请求场景。
    1. 错误处理:axios 提供了丰富的错误处理机制,包括全局的错误处理和局部的错误处理,使得在处理HTTP请求过程中能够更好地应对各种异常情况。
    1. 拦截器支持:axios 允许你在发送请求或接收响应前对它们进行拦截和处理,这为添加公共头部、身份验证等操作提供了便利。

一、安装和配置

1、安装

# npn 安装
npm install axios
# yarn 安装
yarn add axios

在这里插入图片描述

2、配置

axios 不用想其他依赖库需要在 main.ts 中 import,只需要在文件中请求,就可以使用,不过为了更方便的在项目中使用,我们需要通过拦截器为 axios 做统一的封装,在个在下一节会说到。

在 src 目录在创建 api 文件夹,并创建 userApi.ts 文件

import axios from "axios";// 登陆
export function userLogin(data = {}) {return axios.post({url: "/login",data,});
}

二、拦截器介绍和使用

1、拦截器介绍

axios 是一个基于 Promise 的 HTTP 客户端,可用于浏览器和 Node.js。它允许您在发送请求或接收响应时使用拦截器来执行特定的操作。axios 拦截器分为请求拦截器和响应拦截器。

请求拦截器允许您在发送请求之前对其进行修改或添加特定的配置。您可以通过请求拦截器来添加认证信息、设置请求头、转换请求数据等操作。

2、拦截器的使用

请求拦截器

// 添加请求拦截器
axios.interceptors.request.use(function (config) {// 在发送请求之前做些什么config.headers.Authorization = 'Bearer token'return config
}, function (error) {// 对请求错误做些什么return Promise.reject(error)
})

响应拦截器

// 添加响应拦截器
axios.interceptors.response.use(function (response) {// 对响应数据做点什么return response
}, function (error) {// 对响应错误做点什么return Promise.reject(error)
})

三、axios 请求封装

在 utils 文件下创建 service 文件夹,新建四个文件,分别是:config.ts、http.ts、index.ts、types.ts

1、config.ts文件

该文件为 url 等相关配置

const TEST_BASE_URL = '/mock'
const API_BASE_URL = '/api'
const AUTH_BASE_URL = '/auth'const TIME_OUT = 30 * 1000export {TEST_BASE_URL,API_BASE_URL,AUTH_BASE_URL,TIME_OUT
}

2、types.ts文件

该文件为ts 的类型声明

import type { AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'export interface BaseRequestInterceptors<T = AxiosResponse<any>> {requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfigrequestInterceptorCatch?: (error: any) => anyresponseInterceptor?: (res: T) => TresponseInterceptorCatch?: (error: any) => any
}export interface BaseRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {interceptors?: BaseRequestInterceptors<T>loading?: boolean | stringnoToken?: boolean
}

3、http.ts文件

该文件将请求拦截和相应拦截以及相关逻辑全部封装

定义了一个基于 axios 封装的 BaseRequest 类,用于发送 http 请求。具体功能有:

  1. 创建 axios 实例,并允许用户自定义配置;
  2. 支持使用拦截器对请求和响应进行处理;
  3. 支持在请求中添加 token;
  4. 支持在请求中显示 loading;
  5. 提供一系列方法用于发送 get、post、put、delete、patch 请求;
  6. 对不同的 http 错误状态码进行处理,并在错误时显示对应的错误信息。
import axios from "axios";
import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";
import { BaseRequestConfig, BaseRequestInterceptors } from "./types";import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
import { LoadingInstance } from "element-plus/es/components/loading/src/loading";
import { getToken, logout } from "@/utils/tools/user";const DEFAULT_LOADING = false;
let isRelogin = false;class BaseRequest {// axios 实例instance: AxiosInstance;interceptors?: BaseRequestInterceptors;showLoading: boolean | string;loading?: LoadingInstance;constructor(config: BaseRequestConfig) {this.instance = axios.create(config);this.showLoading = config.loading ?? DEFAULT_LOADING;this.interceptors = config.interceptors;// 使用拦截器// 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) => {if (config.params && config.params.responseType) {config.responseType = config.params.responseType;delete config.params.responseType;}if ((config.method === "post" || config.method === "put") &&config.params) {if (config.params.formData) {config.data = config.params.formData;} else {config.data = { ...config.params };}delete config.params;}// console.log('所有的实例都有的拦截器: 请求成功拦截')if (this.showLoading) {const loadingText =typeof this.showLoading == "boolean" ? "加载中" : this.showLoading;this.loading = ElLoading.service({lock: true,text: loadingText + "...",background: "rgba(0, 0, 0, 0.5)",});}return config;},(err) => {console.log("所有的实例都有的拦截器: 请求失败拦截");return err;});this.instance.interceptors.response.use((res) => {// 将loading移除this.loading?.close();if (res.status !== 200) return res;return res.data;},(err) => {// console.log('所有的实例都有的拦截器: 响应失败拦截')// 将loading移除this.loading?.close();// 例子: 判断不同的HttpErrorCode显示不同的错误信息switch (err.response.status) {case 400:ElMessage.error("请求错误(400)");break;case 401:ElMessage.error("未授权,请重新登录(401)");logout();break;case 403:ElMessage.error("拒绝访问(403)");break;case 404:ElMessage.error("请求出错(404)");break;case 408:ElMessage.error("请求超时(408)");break;case 500:ElMessage.error("服务器错误(500)");break;case 501:ElMessage.error("服务未实现(501)");break;case 502:ElMessage.error("网络错误(502)");break;case 503:ElMessage.error("服务不可用(503)");break;case 504:ElMessage.error("网络超时(504)");break;case 505:ElMessage.error("HTTP版本不受支持(505)");break;default: {ElMessage.error(`连接出错(${err.response.status})!`);}}return err;});}request<T = any>(config: BaseRequestConfig<T>): Promise<T> {return new Promise((resolve, reject) => {// 1.单个请求对请求config的处理if (config.interceptors?.requestInterceptor) {// 对config 进行转化config = config.interceptors.requestInterceptor(config as InternalAxiosRequestConfig);}// 携带token的拦截if (!config.noToken && getToken()) {config.headers = {Authorization: getToken(),};}// 2.判断是否需要显示loadingif (config.loading) {this.showLoading = config.loading;}this.instance.request<any, T>(config).then((res: any) => {// 1.单个请求对数据的处理if (config.interceptors?.responseInterceptor) {res = config.interceptors.responseInterceptor(res);}// 2.将showLoading设置true, 这样不会影响下一个请求this.showLoading = DEFAULT_LOADING;if (res.status !== 200) {if (config.responseType == "blob") {resolve(res);return;}let errMsg = res.message || "请求失败";if (res.code == "501") errMsg = "数据中存在敏感词汇, 请修改!";if (res.status == 401) {if (!isRelogin) {isRelogin = true;// prettier-ignoreerrMsg = res.message || '登录状态已过期,您可以继续留在该页面,或者重新登录'ElMessageBox.confirm(errMsg, "系统提示", {confirmButtonText: "重新登录",cancelButtonText: "取消",type: "warning",}).then(() => {isRelogin = false;logout();}).catch(() => {isRelogin = false;});}reject(res);return;}ElMessage({message: errMsg,type: "error",});reject(res);} else {resolve(res);}}).catch((err) => {// 将showLoading设置true, 这样不会影响下一个请求this.showLoading = DEFAULT_LOADING;reject(err);return err;});});}get<T = any>(config: BaseRequestConfig<T>): Promise<T> {return this.request<T>({ ...config, method: "GET" });}post<T = any>(config: BaseRequestConfig<T>): Promise<T> {return this.request<T>({ ...config, method: "POST" });}put<T = any>(config: BaseRequestConfig<T>): Promise<T> {return this.request<T>({ ...config, method: "PUT" });}delete<T = any>(config: BaseRequestConfig<T>): Promise<T> {return this.request<T>({ ...config, method: "DELETE" });}patch<T = any>(config: BaseRequestConfig<T>): Promise<T> {return this.request<T>({ ...config, method: "PATCH" });}
}export default BaseRequest;

4、index.ts文件

使用已经封装好的 BaseRequest

import BaseRequest from './http'
import { TIME_OUT } from './config'const testRequest = new BaseRequest({timeout: TIME_OUT,interceptors: {requestInterceptor: config => {return config},requestInterceptorCatch: err => {// console.log('请求失败的拦截')return err},responseInterceptor: res => {// console.log('响应成功的拦截')return res},responseInterceptorCatch: err => {const errRes = err.responseif (errRes) {return {status: errRes.status,message: errRes.data.message}}return err}}
})export default testRequest

5、使用封装

修改 login 接口

import testRequest from "@/utils/service";
import { TEST_BASE_URL } from "@/utils/service/config";// 登陆
export function userLogin(data = {}) {return testRequest.post({url: TEST_BASE_URL + "/login",data,});
}

四、mock 的使用

1、什么是 mockjs

Mock.js 是一个用于生成模拟数据的 JavaScript 库,它能帮助前端开发人员模拟后端接口数据,尤其适用于前后端分离开发中。通过 Mock.js,开发人员可以轻松地创建和配置虚拟接口,从而使前端能够在后端接口尚未完成或不易获取时进行开发和测试。

Mock.js 提供了丰富的数据模拟功能,包括生成随机的文本、数字、日期、布尔值、数组等等,还能模拟不同情况下的数据返回。使用 Mock.js 可以大大提高前端开发效率,促进前后端协作,是一个强大而实用的前端开发工具。

2、安装

# yarn 安装
yarn add mockjs -D# 这里要特别注意版本,如果版本过高会报错 require 找不到
yarn add vite-plugin-mock@2.9.6 -D 

在这里插入图片描述

3、配置

在根目录创建 mock 文件,并在其中创建 index.ts 文件,增加测试的请求

import { MockMethod } from "vite-plugin-mock";const mockItems: MockMethod[] = [{url: "/mock/login",method: "post",response: () => {return {status: 200,data: {"access_token": "abc",},message: "success",};},},
];export default mockItems;

在 vite.config.ts 文件中

import { viteMockServe } from "vite-plugin-mock";plugins: [vue(),nodePolyfills(),setupExtend(),// 新增代码viteMockServe({mockPath: "./mock",localEnabled: command === "serve"}),// ...
]

4、界面测试 mock

  1. 修改登录按钮逻辑

views文件下 login文件登录逻辑修改如下

const loginClick = () => {loading.value = true;const accessToken = getToken();if (!accessToken) {userLogin().then((res) => {setToken(res.data.access_token);}).then(() => {userLoginFunc();}).catch((err) => {ElMessage.error(err);loading.value = false;});} else {userLoginFunc();}
};
  1. 在登录页面点击登录,触发接口
    在这里插入图片描述
    在这里插入图片描述

总结

本文介绍了在 Vue 项目中使用 axios 进行 HTTP 通信的方法。首先详细介绍了 axios 的安装和配置方法,包括 npm 或 yarn 安装以及文件请求的简单示例。然后讲解了 axios 拦截器的作用和使用方法,分别介绍了请求拦截器和响应拦截器的使用。接着讲解了对 axios 请求的封装,包括 config、types 和http 三个文件的具体内容,以及如何使用封装好的 BaseRequest 类进行请求。最后,介绍了 mockjs 的作用和安装配置方法,展示了 mock 的使用测试请求。上文中的配置代码可在 github 仓库中直接 copy,仓库路径:https://github.com/SmallTeddy/testing-web。

返回列表