【场景方案】如何去设计并二次封装一个好用的axios,给你提供一个好的参考(1.0版)

news2024/11/17 19:41:09

文章目录

  • 前言
  • 文件结构建议
    • 封装的文件结构
    • 接口管理文件
  • 二次封装axios的初始配置
    • init
    • utils
    • webConfig
  • 再封一层
  • 环境配置
  • 使用

前言

以下演示基于vue3与element-plus


文件结构建议

封装的文件结构

在这里插入图片描述

把二次封装axios所有有关的代码全部放在request文件夹中,其中init.js是二次封装的初始配置,index.js是对init.js再进行一次功能拓展封装,我们真正使用时也是用的这个文件,utils.js是存放相关的工具函数,webConfig.js是一些全局的变量。

为什么这么设计:

  • 代码能够与其他功能解耦
  • 方便直接拷贝整个文件夹放到一个新的项目中直接使用

接口管理文件

在这里插入图片描述
接口都单独放在api文件夹中,并且以后端的业务服务模块进行分类。


二次封装axios的初始配置

init

也就是src/request/init.js文件:

// 配置全局得基础配置
import axios from "axios";

// 配置中心
import webConfig from "./webConfig.js"
// 需要的工具函数
import { isCheckTimeout } from "./utils";

// 相关库
import router from "@/router";
import store from "@/store";
import { ElMessage } from "element-plus";

let request = axios.create({
    //1,基础配置
    baseURL: process.env.VUE_APP_BASE_API, // process.env.VUE_APP_BASE_API就是我们在根目录创建对应的环境变量文件里的配置,
    timeout: 5000, // 设置超时时间为5s
})

// 设置请求拦截器
request.interceptors.request.use((config) => {
    // 1 token添加在请求头中,看看项目的token适合放在localStorage里还是vuex中
    let token = localStorage.getItem("token");
    // let token = store.getters.token;

    // 2 首先有些接口是不需要设置token的,那就不用设置token了
    let whiteList = webConfig.whiteListApi // 获取不需要设置token的接口白名单
    let url = config.url
    if (whiteList.indexOf(url) === -1 && token) {
        if (isCheckTimeout()) {
            // 超时了做登出操作
            store.dispatch("user/logout");
            return Promise.reject(new Error("token 失效"));
        }
        // 注入token到请求头中
        config.headers.Authorization = `Bearer ${token}`;
    }

    // 3 配置接口国际化
    config.headers["Accept-Language"] = store.getters.language;

    // 4 配置响应数据类型
    config.headers.responseType = "json"

    return config; // 必须返回配置
}, error => {
    return Promise.reject(new Error(error))
})

// 响应拦截器(处理返回的数据)
request.interceptors.response.use((res) => {
    //响应得统一处理
    let { status, message, data } = res.data; // status 这里的状态码是后端定义的,也就是每次请求都是200,但是后端根据情况返回401,500等状态码
    if (status === 401) {
        router.push("/noAuth")
        return Promise.reject(new Error(message)); // 抛出可以自定义错误提示
    }
    if (status !== 200) {
        ElMessage.error("错误码" + status + ":" + message);
        return Promise.reject(new Error(message));
    }
    return res.data; // res 里面其实有很多东西下面截图
}, error => {
    // 例如断网、跨域、状态码问题的报错
    ElMessage.error(error.message);
    // error.response.status 这里可以拿到接口真正的状态码,还是要看后端是怎么设计接口的
    return Promise.reject(new Error(error));
})

export default request;

其中响应数据res为:

在这里插入图片描述
响应失败的error内容有:

在这里插入图片描述

所需其他的文件

utils

utils.js:

/* 处理token的时效性 */

// 常量设置
// token 时间戳
export const TIME_STAMP = "timeStamp";
// token超时时长(毫秒) 两小时
export const TOKEN_TIMEOUT_VALUE = 2 * 3600 * 1000;

/**
 * 获取时间戳
 */
export function getTimeStamp() {
  return window.localStorage.getItem(TIME_STAMP);
}
/**
 * 设置时间戳(就是当前登陆时获取token的时间)
 */
export function setTimeStamp() {
    window.localStorage.setItem(TIME_STAMP, Date.now());
}
/**
 * 是否超时
 */
export function isCheckTimeout() {
  // 当前时间戳
  var currentTime = Date.now();
  // 缓存时间戳
  var timeStamp = getTimeStamp();
  return currentTime - timeStamp > TOKEN_TIMEOUT_VALUE;
}

webConfig

webConfig.js:

export default {
    whiteListApi: ["/a", "/b"],
    pageSize: [20, 40, 80] // table的页码
}

再封一层

首先要知道为什么还需要进行一次封装,因为单单只对axios做简单的初始化封装还是太过于简陋,未来随着业务的拓展,我们有可能会对接口请求的api做一些功能的拓展。

例如到了某个阶段,我们需要给接口返回的参数添加前端数据缓存。又到了某个阶段,需要阻止接口短时间内重复请求的功能等等。

这时候就很考验我们的代码设计了。

推荐一种链式调用函数的方式去封装,举个例子:

function PromiseFn() { // 用函数封装,因为new Promise就直接执行了
    return new Promise((resolve, reject) => {
        // 这里就写异步操作
        setTimeout(() => {
            if (true) resolve("图片已经被添加了")
            else reject("图片没有被添加")
        }, 1000)
    });
}

function p() {
    let promise = Promise.resolve()

    function fn1(result) { // 功能封装1
        console.log('fn1');
        return Promise.resolve('fn1')
    }

    function fn2(result) { // 功能封装2
        console.log('fn2');
        return Promise.resolve('fn2')
    }

    let arr = [fn1, fn2]

    while (arr.length) {
        promise = promise.then(arr.shift())
    }

    return promise
}

p('1') // fn1 fn2 轮流执行

好好研究你就会发现,我们是相当于把一个一个功能封装函数当做是一个节点,只需要把传入项经过节点处理后,就得到一个封装过的方法。

按照这个思路我们来试试 index.js 怎么写

import request from "./init";

import { ElLoading } from 'element-plus'

// 要点 1 把同步的方法也统一以异步调用处理,保证所有方法的顺序 2 可通过配置关闭制定节点处理

// 默认配置
//{ 
//     nendCache: false, // 是否直接拿取接口的上一个缓存数据(谨慎开启)
//     nendLoading: true, // 全局加载
//     needRequestingOnlyOne: false, // 是否开启,当前接口在请求时,禁止重复请求的情况(谨慎开启),因为有时候我们需要并发请求
// }

let requestApi = (function () {
    let mapCache = new Map(); // 接口返回的数据缓存
    let requestingUrl = []; // 正在发送的请求
    let loadingInstance = null // 全局加载组件

    return function (config) {
        let { url, nendCache, nendLoading, needRequestingOnlyOne } = config
        let promise = Promise.resolve() // 获取一个成功状态的promise

        // 节点:全局加载
        function nodeLoading() {
            if (nendLoading === undefined || nendLoading === true) {
                loadingInstance = ElLoading.service({ fullscreen: true })
            }
            return Promise.resolve({ keepGoing: true, type: 'then' })
        }

        // 节点:是否直接拿取接口的上一个缓存数据
        function nodeCache() {
            if (nendCache === true) {
                // 如果之前已经请求过了,就直接返回缓存的,下面的节点都不用走了ß
                if (mapCache.has(url)) {
                    if (loadingInstance) loadingInstance.close() // 关闭全局加载
                    return Promise.resolve({ keepGoing: false, type: 'then', data: mapCache.get(url) })
                } else {
                    return Promise.resolve({ keepGoing: true, type: 'then' })
                }
            }
            return Promise.resolve({ keepGoing: true, type: 'then' })
        }

        // 节点:接口还在请求过程中,又发了一次,阻止这种情况
        function nodeRequestingOnlyOne() {
            if (needRequestingOnlyOne === undefined || needRequestingOnlyOne === true) {
                // 看看是不是在请求中
                if (requestingUrl.indexOf(url) !== -1) {
                    return Promise.resolve({ keepGoing: false, type: 'catch', data: '请求已经提交' })
                } else {
                    return Promise.resolve({ keepGoing: true, type: 'then' })
                }
            }
            return Promise.resolve({ keepGoing: true, type: 'then' })
        }

        // 节点:发起请求
        async function nodeRequest() {
            let resData = await request({ ...config }) // 发送请求,等待获取数据
            return Promise.resolve({ keepGoing: true, type: 'then', data: resData.data })
        }
		
		// 添加新节点

        // 节点:请求完成后的收尾工作
        function nodeRequestedFinalDo(result = { keepGoing: true }) {

            // 需要抛出错误的情况
            if (result.type === 'catch') {
                return Promise.reject(result.data)
            }

            // 剔除掉正在请求中的接口的标识
            requestingUrl = requestingUrl.filter(item => {
                if (item !== url) {
                    return item
                }
            })

            // 写入接口缓存数据
            mapCache.set(url, result.data)

            // 关闭全局加载
            if (loadingInstance) loadingInstance.close()

            return Promise.resolve(result.data)
        }

        let _handleArr = [nodeLoading, nodeCache, nodeRequestingOnlyOne, nodeRequest, nodeRequestedFinalDo] // 需要经过处理的节点

        function startNodeList() {
            while (_handleArr.length > 0) {
                let fn = _handleArr.shift() // 获取当前节点
                function _final(result = { keepGoing: true }) { // keepGoing 为false表示当前节点不执行,跳到下一个节点
                    if (!result.keepGoing) { // 后面keepGoing为false的节点都走这里
                        return Promise.resolve(result.data || result) // 如果是中途有keepGoing为false的需要取data
                    }
                    // keepGoing为true节点都走这里
                    return fn.call(this, result)
                }
                promise = promise.then(_final)
            }
        }
        startNodeList()
        return promise
    }
})()
export {
    requestApi as request, // 进过三次封装的
    request as initRequest // 不进过第三次封装的
}

这片代码对js的基础要求很高,属于高阶用法了,多看看!


环境配置

之前写init.js的时候,需要配置接口请求基本地址的环境变量。需要在目录中创建:

在这里插入图片描述
然后分别为

# 标志
ENV = 'development'

# base api
VUE_APP_BASE_API = '/api'
# 标志
ENV = 'production'

# base api
VUE_APP_BASE_API = 'https://mock.mengxuegu.com/mock/625c05ab66abf914b1f1bf10/crm'

使用

ok都写完后就可以使用了,在api/user.js中举例子:

import { initRequest, request } from "../request"

// 用二次封装的get请求,参数以对象的形式传入,axios会自动帮我们拼接在请求地址中
export const getUsersAllType = (params) => { 
    return initRequest({
        url: "/usersAllType",
        method: "get",
        params: {
            ...params
        },
    })
}

// 用三次封装的post请求
export const getUserProfile = (params) => { 
    return request({
        url: "/profile",
        method: "post",
        params: {
            ...params
        },
        needRequestingOnlyOne: true // 开启具体某项功能
    })
}

完成~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【系统移植】SD卡 分区

目录 1、分区框架 2、清空磁盘分区 3、开始分区 4、格式化分区 1、分区框架 制作SD卡时,我们需要对 SD 卡进行分区,每个区存入对应的内容,整体框架如下: 第二扇区:uboot程序从这里开始存储,最开始的第…

Class 07 - 功能包的安装和 tidyverse 介绍

Class 07 - 功能包的安装和 tidyverse 介绍 tidyverse 简介功能包(package)的安装tidyverse 的安装 功能包(package)的加载tidyverse 的加载 功能包(package)的更新tidyverse 核心功能browseVignettes 函数…

组合数学第三讲

composition(组成) k-composition: 20块巧克力分给4个小朋友,有几种分法? 隔板法,19个间隙插入3个板, 推广:n块分给k个 weak k-composition: 20块巧克力分给4个小朋友,每…

Ajax基础知识点总结

努力前进 目录 为什么需要Ajax? 1.提高用户体验,实现局部刷新效果 2.提高性能和降低带宽消耗 什么是 Ajax Ajax 的工作原理 Ajax的工作过程分为以下几个步骤: 最基础的Ajax代码演示: open函数中的参数分析: 在Ajax中ready…

Dijkstra单源最短路

Dijkstra单源最短路径 什么是单源最短路径 描述:给定一个带权有向图G (V,E),其中每条边的权时非负数。另外,给定V中的一个顶点,称为源。现在要计算从源到所有其他各顶点的最短路长度。这里路的长度是指路上各边权之…

数据在 Mocaverse 项目启动过程中是如何发挥作用的

日期:2023年5月 数据源: Mocaverse Realm Ticket Collection Airdrop & Mocaverse Optimizes an NFT Project at Launch & Beyond NFT 是 Web3 社区的基础。它们是区块链游戏、DAO 和 metaverses 的入场券,以及成为社区参与者的数字…

[创业之路-69]:对管理理念的理解和解读

目录 前言: 一、管理者与领导者的区别 二、管理活动的分类 三、业务管理:以终为始 3.1 业务目标到高效执行 (1)先明确要做哪些正确的事 》 需求分析、目标 (2)再明确怎样正确的做事 》 设计、实现 …

chatgpt赋能Python-python3_pygame

Python3 Pygame:游戏引擎进入开发者的时代 Python是一种常用的编程语言,有许多优秀的库和框架,而其中Pygame是许多游戏开发者的首选。Pygame是一个用Python编写的开源软件包,旨在帮助游戏制作者创建交互式游戏和媒体程序。在本篇…

OpenCV基础操作(2)OpevCV算术运算

OpenCV基础操作(2)OpevCV算术运算 import cv2 as cv import numpy as np一、图像的基础操作 1、获取并修改像素值 你可以根据像素的行和列的坐标获取他的像素值。 对 BGR 图像而言,返回值为 B,G,R 的值。对灰度图像而言,会返回他…

微信小程序node+vue+uniapp课程在线答疑学习答题考试系统

系统主要分为管理员和学生、教师三部分,管理员服务端:首页、个人中心、学生管理、教师管理、课程资源管理、课程类型管理、学习记录管理、系统管理,教师服务端:首页、个人中心、课程资源管理、学习记录管理、试题管理、试卷管理、…

JAVA期末考内容知识点的梳理

作者的话 前言:这些都是很基本的,还有很多没有写出来,重点在于考试复习,包括后四章的内容 前面内容请参考JAVA阶段考内容知识点的梳理 一、集合、流 课堂总结1集合 集合概念: 保存和盛装数据的容器,将许多…

maven的常用命令clean/package/install/deploy

如标题,下面放图: 這就是一个pom对应的maven操作命令, 那这些命令中,最常用的打包项目的命令是什么? 两种最常用打包方法: 1.先 clean,然后 package2.先 clean,然后install 下面…

实验四 车辆定位导航

有想自己动手的同学可在末尾看教程 【实验目的】 1、了解全球定位导航系统的定位原理和电子地图技术,掌握电子地图API使用方法。 2、了解导航数据报文数据格式,解析导航数据并在电子地图上进行导航应用。 【实验性质】 验证性实验。 【实验要求】 1、相…

【动态规划专栏】--基础-- 动态规划经典题型

目录 动态规划 动态规划思维(基础) 状态表示(最重要) 状态转移方程(最难) 初始化(细节) 填表顺序(细节) 返回值(结果) 1、第 …

C++实现日期类Date(超详细)

个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】💌 本专栏旨在记录C的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长&…

C++笔试笔记2

C笔试笔记2 百富计算机的笔试 const限定符:首先作用于左边,如果左边没东西,就作用于右边。 const int: 左边没有内容,所以const作用于右边,就是“整型常量”。等同于int const; int * const&am…

NSS LitCTF部分wp

web 1、PHP是世界上最好的语言!! 直接cat flag flagNSSCTF{11eaebe0-3764-410d-be83-b23532a24235} 2、这是什么?SQL !注一下 ! 直接查询,发现注入点是id 使用sqlmap列出所以数据库 ​sqlmap -u "h…

亚马逊,shopee,lazada卖家如何组建自己的测评团队

测评补单,这个话题在如今不管国内还是国外的电商行业已经是众所周知,它能够快速帮助自己的产品添加评论,获取排名,打造爆款,可以让用户更加真实、清晰、快捷的了解产品,以及产品的使用,快速上手…

低代码,或将颠覆开发行业?

前言 传统的软件开发过程往往需要耗费大量的时间和精力,因为开发人员需编写复杂的代码以完成各种功能。 低代码行业的发展,正好解决了这个问题,让复杂的代码编写一去不复返了。 文章目录 前言引入强大的平台总结 引入 低代码平台 是一种通过可…

【团购-HeaderView Objective-C语言】

一、那么,我们看上面这个东西,这就是我们保存在HeaderView里面的一个东西吧, HeaderView里面的一个东西, 1.因为这个东西,看起来,是不是也是一个这个样子,也是一个固定的样子啊, 所以我们这个UITableView的HeaderView,也可以使用一个xib来描述,也可以使用xib来描述,…