vue3实战三、Axios封装结合mock数据,vite跨域及环境变量配置 入口

Axios封装、调用mock接口、Vite跨域及环境变量配置

封装Axios对象调用mock接口数据

因为项目中有很多接口要通过Axios发送异步请求,所以需要封装一个axios对象,自己封装的Axios后面可以时候axios中提供的拦截器,参考官方文档

第一步、安装axios,处理一部请求

c 复制代码
npm install axios

第二步、创建request.ts文件

src 目录下创建utils目录,然后 utils 目录下创建 request.ts 文件

typescript 复制代码
import axios from 'axios';
import type { AxiosInstance } from 'axios';
import { ElMessage } from 'element-plus';
// 手动创建一个 axios 对象, 参考: https://github.com/axios/axios#creating-an-instance
const request: AxiosInstance = axios.create({
  //baseURL: 'https://mock.xxx.com/mock/64fa8039e70b8004a69ea036/hsk-admin',
  // 根据不同环境设置 baseURL, 最终发送请求时的URL为: baseURL + 发送请求时写URL ,
  // 比如: `baseURL: '/dev-api'` ,当请求 get('/test'), 最终发送请求是: /dev-api/test
  // baseURL: '/dev-api',
  // 获取项目根目录下 .env.xxxx 文件中的环境变量值
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 20000, // 请求超时的毫秒数,请求时间超过指定值,则请求会被中断
});
// 请求拦截器
request.interceptors.request.use(config => {
  // 在此处可向请求头加上认证token
  return config;
}, error => {
  // 出现异常, catch可捕获到
  return Promise.reject(error);
})
// 响应拦截器
request.interceptors.response.use(response => {
  // console.log('响应拦截器', response);
  const res = response.data;
  // 20000 正常响应,返回响应结果给调用方
  if (res.code === 20000) {
    return res;
  }
  // 非正常响应弹出错误信息,
  ElMessage.error(res.message);
  return Promise.reject(res);
}, error => {
  // 处理响应错误
  const { message, response } = error;
  if (message.indexOf('timeout') != -1) {
    ElMessage.error('网络超时!');
  } else if (message == 'Network Error') {
    ElMessage.error('网络连接错误!');
  } else {
    if (response.data) ElMessage.error(response.statusText);
    else ElMessage.error('接口路径找不到');
  }
  return Promise.reject(error);
});
export default request; // 导出 axios 对象

第三步、本地模拟mock数据接口

前后端分离开发过程中,后端数据接口还没有写出来,前端可以使用mock模拟假数据进行先一步页面的开发,使用mockjs模拟后端接口,可随机生成所需要的数据,模拟对数据的增删查改,mock支持丰富的数据类型,支持生成随机的文本数字布尔值日期邮箱链接图片颜色等,拦截Ajax请求不需要修改既有代码可以拦截,返回模拟的响应数据。

项目中使用mockjs的时候,首先确保安装了axiosmock,上面第一步的时候已经安装了axios数据,现在开始进行安装mock

typescript 复制代码
npm install -D mockjs

在项目的src文件夹下新建一个文件夹用来存放mock模拟的数据,一般我们放在将mock模拟的数据( /src/mock/index.js)这个文件中,这里以此为例。

typescript 复制代码
//这里是我使用本地的服务器商品接口地址模拟的数据
import { mock } from 'mockjs'
let data = mock({
  "code": 20000,
  "message": "查询成功",
  "data": [
    { "name": "小梦", "age": 18 },
    { "name": "涛姐", "age": 32 },
    { "name": "林志玲", "age": 48 }
  ]
})
mock(/test/, 'get', () => {
  return data
})

模拟完数据后,在入口主文件 main.js 中引入这个模拟数据的文件

typescript 复制代码
import "./mock/index.js"

第四步、测试axios+mock接口是否可以调用

src/ 下创建 /api/test.ts 目录和文件, 调用接口代码如下:

typescript 复制代码
import request from "@/utils/request";
export function test1() {
  // 测试1: 调用 get 方式发送get请求
  request.get("/test").then(response => {
    console.log("get1", response);
  }).catch(error => {
    console.log('error', error);
  });
}

export function test2() {
  // 测试2, 使用对象形式传入请求配置,如 请求url, method,param
  request({
    url: `/test`,
    method: "GET"
  }).then(response => {
    console.log("get2", response);
  }).catch(error => {
    console.log(error);
  });
}

app.vue页面引入接口并调用:

typescript 复制代码
<template>
  <div>
    <el-icon><ele-Search /></el-icon>
    <SvgIcon name="ele-Search"></SvgIcon>
    <el-button type="primary">Primary</el-button>
  </div>
</template>
<script lang="ts" setup>
// 导入 test.ts,
import { test1, test2 } from "../src/api/test.ts";
// 调用方法发送请求
test1();
test2();
</script>
<style lang="scss">
// 编写 scss 代码
</style>

效果: 测试三、通过 api 方法返回请求的 Promise 对象,然后在调用方通过then 获取响应数据,api\test.ts 新增如下代码:

typescript 复制代码
import request from "@/utils/request";
// 返回 Promise
export function getList() {
  const req = request({
    url: `/test`,
    method: "GET"
  });
  // console.log(req) // Promise
  return req;
}

App.vue 中导入 test.ts

typescript 复制代码
<script setup lang='ts'>
// 导入 test.ts,调用方法发送请求
import { getList } from "@/api/test";
import { onMounted } from "vue";
onMounted(() => {
  loadData();
  loadData2();
});
function loadData() {
  getList()
    .then((response: any) => {
      console.log("loadData", response);
    })
    .catch((error: Error) => {
      console.log(error);
    });
}
// 使用 async+await
async function loadData2() {
  const response = await getList();
  console.log("loadData2", response);
}
</script>

后面数据访问都采用测试三这种方式。

第五步、自行扩展 axios 返回的数据类型 axios.d.ts

  1. 使用axios发送请求接口后,axios生命的响应对象AxiosResponse中的属性并不是我们想要的,我们需要自行扩展为我们需要相应的数据类型。

    typescript 复制代码
    /* eslint-disable */
    import * as axios from 'axios';
    // 自行扩展 axios 返回的数据类型
    declare module 'axios' {
      export interface AxiosResponse<T = any> {
        code: number;
        message: string;
        data: T;
      }
    }

    declare 定义的接口类型,在 SFCts 文件中不需要导入,相当于全局接口,直接引用接口类型。但是要告知 ts 文件在哪里

  2. 自动加载 *.d.ts文件,修改 tsconfig.app.json (旧版本在 tsconfig.json 中)文件的 includes 选项值:追加 "src/**/*.d.ts"

typescript 复制代码
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.d.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

注意:添加后,重启编辑器VsCode,不然可能无法识别到*.d.ts文件

跨域解决

什么是跨域

前后端分离时,前端和后端API服务器可能不在同一台主机上面,就存在跨域问题,浏览器限制了跨域访问,违反了同源策略【协议,域名,端口】都要相同,其中一个不同都会产生跨域。

跨域示例

  • 前端部署在http://127.0.0.1:8888/即本地ip端口8888上面
  • 后端API部署在http://127.0.0.1:9999/即本地ip端口999上面

他们的IP一样,但是端口不一样就会存在跨域问题。

跨域解决

【跨域解决参考文档入口】

  • 通过代理请求的方式解决,将 API 请求通过代理服务器请求到 API 服务器。
  • 开发环境中,在 vite.config.ts 文件中使用 server.proxy 选项进行代理配置。
  • 生产环境,可采用 nginx代理 解决跨域问题。

跨域实操

  1. vite.config.ts 文件中使用 server.proxy 选项进行代理配置。
typescript 复制代码
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
  // 开发服务器选项,参考:https://cn.vitejs.dev/config/server-options.html#server-host
  server: {
    open: true, //启动服务时自动打开浏览器访问
    port: 8888, //端口号, 如果端口号被占用,会自动提升1
    proxy: { // ++++++++++++++++++ 解决跨域问题
      '/dev-api': { // 匹配 /dev-api 开头的请求,
        // 目标服务器, 代理访问到https://mock.mengxuegu.com/mock/6621e5cbfaa39b4567596484/adminPower
        target: 'https://mock.mengxuegu.com/mock/6621e5cbfaa39b4567596484/adminPower',
        // 开启代理:在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,
        // 这样服务端和服务端进行数据的交互就不会有跨域问题
        changeOrigin: true,
        // 将 /dev-api 替换为 '',也就是 /dev-api 会移除,
        // 如 /dev-api/test 代理到https://mock.mengxuegu.com/mock/6621e5cbfaa39b4567596484/adminPower
        rewrite: (path) => path.replace(/^\/dev-api/, ''),
      },
    }
  },
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
  1. src\utils\request.ts 中的 baseURL 修改:

    typescript 复制代码
    const request: AxiosInstance = axios.create({
      baseURL: '/dev-api',
      timeout: 20000, // 请求超时的毫秒数,请求时间超过指定值,则请求会被中断
    });
  2. 测试:访问http://127.0.0.1:8888/,查看控制台响应了数据,说明代理成功

Vite配置环境变量及模式

Vite 配置环境变量

环境分为testprodtest环境,他们请求的后台接口获取数据,不同环境的接口的URL不同,所以要为不同环境配置不同的接口URL,通过路径前缀进行匹配。

  1. 在根目录下创建.env.development.env.production文件,为了防止意外地讲一些环境变量泄漏到客户端,只有以VITE_为前缀的环境变量才会通过import.meta.env以字符串形式暴露给经过Vite处理的代码使用。

    如:

    • VITE_DEV_API=/dev-api可以通过 import.meta.env.VITE_DEV_API访问,返回/dev-api
    • WY_DEV_API=/dev-api不能通过 import.meta.env.WY_DEV_API访问,返回undefined
  2. .env.development 文件配置(注意开发和生产环境配置不要搞反了)VITE_APP_SERVICE_URL 值更改为你自已配置的 项目后端的 接口服务地址。

    typescript 复制代码
    # 使用 VITE_ 开头的变量会被 vite 暴露出来
    # 定义请求的基础URL, 方便跨域请求时使用
    VITE_APP_BASE_API=/dev-api
    # 接口服务地址
    VITE_APP_SERVICE_URL= https://www.****.com/
  3. .env.production 文件配置

    typescript 复制代码
    # 使用 VITE_ 开头的变量会被 vite 暴露出来
    # 定义请求的基础URL, 方便跨域请求时使用
    VITE_APP_BASE_API=/pro-api
    # 接口服务地址
    VITE_APP_SERVICE_URL= https://www.****.com/
  4. 测试是否配置成功,在 request.ts 中添加以下代码,看下浏览器控制台是否会输出,在项目任意模块文件中,都可以使用 通过 import.meta.env.VITE_APP_BASE_API 获取值:

    typescript 复制代码
    import axios from 'axios';
    import type { AxiosInstance } from 'axios';
    import { ElMessage } from 'element-plus';
    console.log(import.meta.env.VITE_APP_BASE_API)
    const request: AxiosInstance = axios.create({
      baseURL: '/dev-api',
      timeout: 20000, // 请求超时的毫秒数,请求时间超过指定值,则请求会被中断
    });

Vite配置环境模式

默认情况下,开发服务器 ( dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production(生产) 模式。 查看package.json ,当执行 vite build 时,它会自动加载 .env.production 中可能存在的环境变量:

typescript 复制代码
VITE_APP_BASE_API=/pro-api

utils/request.ts 中可以使用import.meta.env.VITE_APP_BASE_API 获取引用。在某些情况下,若想在 vite build 时运行不同的模式来渲染不同的标题,你可以通过传递 --mode 选项标志来覆盖命令使用的默认模式。

例如,如果你想在 production(生产)模式下构建应用:

  1. 项目根目录下新建一个 .env.production文件:

  2. package.json 中添加一个prod选项运行 vite --mode production命令

  3. 启动prod环境查看console.log(import.meta.env.VITE_APP_BASE_API) 打印结果:

重构代理配置

重构vite.config中的server.proxy代理配置,在vite.config.ts中无法通过import.meta.env.xxx获取到VITE_环境变量,解决此问题需要用到vite中提供的loadEnv方法来读取环境变量。

typescript 复制代码
import { defineConfig, loadEnv } from 'vite'
// mode:获取 --mode 指定的模式,process.cwd()项目根目录,下面 `env` 相当于 `import.meta.env`
const env = loadEnv(mode, process.cwd());
  1. 重构vite.config中的server.proxy代理配置

    typescript 复制代码
    import { fileURLToPath, URL } from 'node:url'
    // 1. 导入 loadEnv
    import { defineConfig, loadEnv } from 'vite'
    import vue from '@vitejs/plugin-vue'
    // 2. 向 defineConfig 传递对象改为传递方法,并返回配置对象
    export default defineConfig(({ mode }) => {
      // mode:获取 --mode 指定的模式,process.cwd()项目根目录,下面 `env` 相当于 `import.meta.env`
      const env = loadEnv(mode, process.cwd());
      return {
        // 开发服务器选项
        server: {
          open: true, //启动服务时自动打开浏览器访问
          port: 8888, //端口号, 如果端口号被占用,会自动提升1
          proxy: {
            // '/dev-api': { // 匹配 /dev-api 开头的请求,
            [env.VITE_APP_BASE_API]: { // 引用变量作为key时,要加中括号[]
              // 目标服务器
              target: env.VITE_APP_SERVICE_URL,
              // 开启代理
              changeOrigin: true,
              rewrite: path => path.replace(new RegExp(`^${env.VITE_APP_BASE_API}/`), '')
            },
          }
        },
        plugins: [
          vue(),
        ],
        resolve: {
          alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
          }
        }
      }
    });
  2. 修改 utils/request.ts 文件配置: baseURL: import.meta.env.VITE_APP_BASE_API

    typescript 复制代码
    import axios from 'axios';
    import type { AxiosInstance } from 'axios';
    import { ElMessage } from 'element-plus';
    console.log(import.meta.env.VITE_APP_BASE_API)
    const request: AxiosInstance = axios.create({
      baseURL: import.meta.env.VITE_APP_BASE_API,
      timeout: 20000, // 请求超时的毫秒数,请求时间超过指定值,则请求会被中断
    });
  3. 重启看一下效果,接口是否调用到

完结~

下一篇:Vue3实战四、项目布局及布局功能实现>>>

相关推荐
gnip1 小时前
链式调用和延迟执行
前端·javascript
SoaringHeart1 小时前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.1 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu1 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss2 小时前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师2 小时前
React面试题
前端·javascript·react.js
木兮xg2 小时前
react基础篇
前端·react.js·前端框架
ssshooter2 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT利刃出鞘3 小时前
HTML--最简的二级菜单页面
前端·html
yume_sibai3 小时前
HTML HTML基础(4)
前端·html