您现在的位置是:首页 >其他 >Vue3全家桶之网络请求网站首页其他

Vue3全家桶之网络请求

江拥羡橙 2024-06-26 14:23:46
简介Vue3全家桶之网络请求

我们都知道,自从前后端分离后,业务逻辑数据都由后端进行维护处理,前端通过网络请求到数据后,进行展示渲染,在实际开发中,我们有常用的axios之类的网络请求库来帮助我们处理相关的请求。

我们接下来就以axios为例,进行网络请求的二次封装。

axios的引用

首先我们在项目中安装下axios:

npm install axios

安装好依赖后,在使用的地方直接引入就可以了:

<script setup>
    import axios from 'axios';
    ...
    axios({
        url: '',
    })
</script>

打开页面运行一下,可以看到axios已经执行了,只是没有正确的请求地址,所以报错404了。

我们用node来写个简单的接口服务,模拟下接口请求的过程,创建server.js文件,node不是我们的课程内容,这里就直接粘贴代码了,代码如下:

const express = require('express');
const http = require('http');

const app = express();
const server = http.createServer(app);

app.get("/api/test",(req, res)=>{
    res.send({state:true, code:200, message:"接口请求成功", data:{}})
})

server.listen(9527);
console.log('服务已经启动,端口9527');

在server.js文件夹下,运行node server.js启动服务。

有了服务后,我们就可以修改下axios的请求地址为http://127.0.0.1:9527/api/test,在浏览器中直接打开这个地址是可以获取到返回结果的,说明我们的服务没有问题,但在js中我们都知道这样请求会报跨域问题。

我们可以通过代理的方式解决跨域的问题,因为使用的前端构建工具是vite,添加proxy代理需要在vite.config.js中,在vite.config.js中添加如下代码:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server:{
    proxy:{
      // 添加代理
      '/api': 'http://127.0.0.1:9527'
    }
  }
})

将axios请求的url删除域名端口号部分,运行后可以看到,成功接收到了后端返回的数据信息。

这就是axios的简单使用,axios并不需要app.use来使用,结合前面的章节,说明axios并没有install方法,而我们在开发过程中,一般也不会直接去使用axios,而是在axios的基础上进行二次封装使用,那么要怎么封装呢?

axios的二次封装

很多关于axios封装的代码都会在main.js文件中进行全局属性的配置,比如设置axios的超时时间:

axios.defaults.timeout = 20000;

虽然这样配置并没有什么问题,但网络请求一般在我们的开发中会作为一个单独模块进行封装,对外统一接口,这样的好处是我们可以在一个集中的地方进行网络请求的相关配置,还可以对请求进行一些预处理和返回结果的拦截处理,也不会与main.js文件中本身的逻辑混合。

我们在api文件夹下封装我们的网络请求模块,新增index.js文件:

import axios from "axios";

export default axios;

目前我们只是引入了axios并进行导出,在这个模块中,我们要完成以下功能的封装:

  1. 添加请求和响应的拦截
  2. 增加重复请求的取消
  3. 对外统一接口

接下来我们就开始依次完成这些功能的封装。

首先思考下,添加请求和响应的拦截目的是什么?一般的网站都存在登录模块,需要用户登录后才可以访问网站内容,那服务是如何判断用户身份和登录状态,相信大家都知道使用token,在用户操作发送请求时携带用户的token信息到后端进行验证。我们肯定不希望在每个请求中都要添加token处理的代码,那就可以在请求拦截器中进行统一处理。

相应的,如果我们对接口返回的结果有统一的处理逻辑,比如对登录失效的错误码进行跳转登录页的操作,也可以放在我们的响应拦截中处理。

总结一下,当我们的网络请求存在相同的处理逻辑时,可以在统一的拦截入口中进行处理,保证对每一个请求都生效。

我们可以通过axios.interceptors.request.useaxios.interceptors.response.use来分别对请求和响应实现拦截:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    console.log('请求拦截', config);
    return config;
}, function (error) {
    return Promise.reject(error);
});

// 响应拦截
axios.interceptors.response.use(function (response) {
    console.log('响应拦截', response);
    return response;
}, function (error) {
    return Promise.reject(error);
});

我们可以获取到请求的config和响应的response信息,headers中就可以添加token信息,当然我们也可以根据其他参数进行更多的处理,比如取消重复请求。

在网页中,如果一些触发事件没有增加节流防抖,可能会出现用户多次点击,这样的重复并没有什么意义反而会消耗资源,我们在axios的请求中,可以通过内部提供的CancelToken来取消重复的网络请求。

先来看一下取消重复请求的原理,我们对每个请求生成一个独一无二的key值,在发送请求的时候,将这个key值保存起来,当有重复的请求发送时,我们判断当前key值的请求正在进行中,就调用aixos提供的取消请求的方法取消当前请求,请求返回后,再将key值从保存中移除,让下一次请求可以成功发送。

原理看上去很简单,增加了一个开关来控制请求得发送,我们来看下具体的实现。

首先我们需要一个独一无二的key值来区分当前的请求,对相同的请求过滤,相同的概念不仅仅是指请求的路径方法相同,而是要包括参数也要相同,比如在查询列表的时候,用户可能修改了查询条件再次调用,两次调用会得到不同的结果,这就是两个不同的请求了。

所以我们可以根据请求的地址,方式,参数,统一计算出当前请求的md5值作为key:

import md5 from "md5";

const getRequestKey = (config) => {
    if ( !config ){
        // 如果没有获取到请求的相关配置信息,根据时间戳生成
        return md5(+new Date());
    }
    const data = typeof config.data === 'string' ? config.data : JSON.stringify(config.data);
    return md5(config.url + '&' + config.method + '&' + data);
};

通过getRequestKey方法,在请求拦截中,就可以根据请求的config计算出当前请求的key值了,除了getRequestKey外,我们还需要能够检查key是否存在的方法以及删除key的方法。

// 存储key值
const pending = {};
// 检查key值
const checkPending = (key) => !!pending[key];
// 删除key值
const removePending = (key) => {
    delete pending[key];
};

现在我们已经可以计算请求的key值,并且可以判断是否有相同的key值正在请求中以及删除完成了请求的key值。

接下来只需要在请求拦截器中存储判断key值,调用axios的取消请求方法即可:

const CancelToken = axios.CancelToken;

// 请求拦截器
axios.interceptors.request.use(function (config) {
    // 计算当前请求key值
    const key = getRequestKey(config);
    if ( checkPending(key) ) {
        // 重复请求则取消当前请求
        const source = CancelToken.source();
        config.cancelToken = source.token;
        source.cancel('重复请求');
    } else {
        // 加入请求字典
        pending[key] = true;
    }
    return config;
})

// 响应拦截器
axios.interceptors.response.use(function (response) {
    // 请求完成,删除请求中状态
    const key = getRequestKey(response.config);
    removePending(key);
    return response;
})

我们给server中的接口响应增加3秒的延迟,看下重复请求是否被取消了。

app.get("/api/test",(req, res)=>{
    // 使用setTimeout延迟接口的返回
    setTimeout(() => {
        res.send({state:true, code:200, message:"接口请求成功", data:{}})
    }, 3000);
})

我们还可以进一步将取消请求和路由跳转结合,在路由跳转的时候取消所有请求,大家可以自己去尝试下。

完成了axios的一些拦截配置后,我们还需要对外统一get,post等方法:

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url, params = {}, config = {}) {
    return axios.get(url, {
                params: params,
                ...config
           })
}

/**
 * post方法,对应post请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function post(url, params, config = {}) {
    return axios({
                url,
                method: 'post',
                data: params,
                ...config
           })
}

除了上面对重复请求的封装,在实际开发过程中,我们还会遇到跨域,超时,错误码处理等功能,这些都是可以在axios封装的时候进行处理。

// 设置请求头
axios.defaults.headers['xxx'] = 'xxx';
// 设置接口超时时间
axios.defaults.timeout = 30000;
// 配置请求的根路径
axios.defaults.baseURL = 'xxx';
// 跨域时,是否携带用户凭证
axios.defaults.withCredentials = true

总结

本节中,我们介绍了Vue中如何进行网络请求,引用axios进行了二次封装,在二次封装中,我们完成了对请求和响应的拦截,重复请求的取消并对外提供统一的接口调用。

网络请求是前端与后端沟通的重要手段,不仅仅是我们在开发过程中引入一个组件发送一个请求这么简单,在这个过程中浏览器替我们实现了很多内容,从请求的构建到DNS的解析,TCP和HTTP的连接建立到协议的组成部分,最终请求到达服务器进行网络响应,网络请求是一个很大的内容,这中间有太多的细节可以深入去聊,希望大家在课余不妨去梳理下网络请求的整个流程,丰富自己的知识图谱。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。