前端新手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 是一个流行的基于PromiseHTTP 客户端,可用于浏览器和 Node.js 环境。它提供了许多优点,包括易用性、可靠性和在处理 HTTP 请求时的灵活性。

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

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

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

一、安装和配置

1、安装

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

在这里插入图片描述

2、配置

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

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

import axios from "axios";

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

二、拦截器介绍和使用

1、拦截器介绍

axios 是一个基于 PromiseHTTP 客户端,可用于浏览器和 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 * 1000

export {
  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) => InternalAxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (res: T) => T
  responseInterceptorCatch?: (error: any) => any
}

export interface BaseRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: BaseRequestInterceptors<T>
  loading?: boolean | string
  noToken?: 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.判断是否需要显示loading
      if (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-ignore
                errMsg = 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.response
      if (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 [email protected] -D 

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

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. 在登录页面点击登录,触发接口
    前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第十章 请求配置 (Axios请求和响应拦截)_第2张图片
    前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第十章 请求配置 (Axios请求和响应拦截)_第3张图片

总结

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

你可能感兴趣的:(前端新手项目指北系列文章,前端,sass,css)