目录
  • 前言
  • 一、axios 的依赖安装与处理  
    • 1. 依赖安装
    • 2. 全局 axios 封装
    • 3. 实际使用
  • 二、 mock.js 的依赖安装与处理  
    • 1. 安装依赖
    • 2. 新建 mock 所需的文件
  • 三、结合使用
    • 总结

      前言

      今天我们一起来看一看 vue3+ts如何优雅的封装axios,并结合 mock.js 实现敏捷开发;

      但是我们要注意区分 Axios 和 Ajax :

      Ajax 是一种技术统称,技术内容包括:HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的XMLHttpRequest,用于浏览器与服务器之间使用异步数据传输(HTTP 请求),做到局部请求以实现局部刷新,使用是基于 XMLHttpRequest 进行使用;

        Axios 是 一个基于 promise 的 HTTP 库,是一个是第三方库

      今天主要技术栈:vue3,ts,axios,mock.js,elementPlus

      一、axios 的依赖安装与处理  

      1. 依赖安装

      使用异步网络请求肯定离不开loading、message 等提示,今天我们配合 elementPlus 一起使用;

      // 安装axios 
      npm install axios --save
       
      // 安装 elementPlus
      npm install element-plus --save

      2. 全局 axios 封装

       src 目录下 utils 目录下,新建 request.ts,因为使用的是TS,需要提前定义数据格式:

      • 定义请求数据返回的格式,需要提前确认好
      • 定义 axios 基础配置信息
      • 请求拦截器:所有请求最先到达的地方,我们可以在此自定义请求头信息(比如:token、多语言等等)
      • 响应拦截器:返回数据最先到达的地方,我们可以在此处理异常信息(比如:code为401重定向至登录、code为500提示错误信息)
      import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
      import { ElMessage, ElLoading, ElMessageBox } from "element-plus";
       
      // response interface { code, msg, success }
      // 不含 data
      interface Result {
          code: number,
          success: boolean,
          msg: string
      }
       
      // request interface,包含 data
      interface ResultData<T = any> extends Result {
          data?: T
      }
       
      enum RequestEnums {
          TIMEOUT = 10000, // 请求超时 request timeout
          FAIL = 500, // 服务器异常 server error
          LOGINTIMEOUT = 401, // 登录超时 login timeout
          SUCCESS = 200, // 请求成功 request successfully
      }
       
      // axios 基础配置
      const config = {
          // 默认地址,可以使用 process Node内置的,项目根目录下新建 .env.development
          baseURL: process.env.VUE_APP_BASE_API as string,
          timeout: RequestEnums.TIMEOUT as number, // 请求超时时间
          withCredentials: true, // 跨越的时候允许携带凭证
      }
       
      class Request {
          service: AxiosInstance;
       
          constructor(config: AxiosRequestConfig) {
              // 实例化 serice
              this.service = axios.create(config);
       
              /**
               * 请求拦截器
               * request -> { 请求拦截器 } -> server
               */
              this.service.interceptors.request.use(
                  (config: AxiosRequestConfig) => {
                      const token = localStorage.getItem('token') ?? '';
                      return {
                          ...config,
                          headers: {
                              'customToken': "customBearer " + token
                          }
                      }
                  },
                  (error: AxiosError) => {
                      // 请求报错
                      Promise.reject(error)
                  }
              );
       
              /**
               * 响应拦截器
               * response -> { 响应拦截器 } -> client
               */
              this.service.interceptors.response.use(
                  (response: AxiosResponse) => {
                      const { data, config } = response;
                      if (data.code === RequestEnums.LOGINTIMEOUT) {
                          // 表示登录过期,需要重定向至登录页面
                          ElMessageBox.alert("Session expired", "System info", {
                              confirmButtonText: 'Relogin',
                              type: 'warning'
                          }).then(() => {
                              // 或者调用 logout 方法去处理
                              localStorage.setItem('token', '');
                              location.href = 'https://www.freexyz.cn/'
                          })
                      }
                      if (data.code && data.code !== RequestEnums.SUCCESS) {
                          ElMessage.error(data);
                          return Promise.reject(data);
                      }
                      return data
                  },
                  (error: AxiosError) => {
                      const { response } = error;
                      if (response) {
                          this.handleCode(response.status);
                      }
                      if (!window.navigator.onLine) {
                          ElMessage.error("网络连接失败,请检查网络");
                          // 可以重定向至404页面
                      }
                  }
       
              )
          }
       
          public handleCode = (code: number): void => {
              switch (code) {
                  case 401:
                      ElMessage.error("登陆失败,请重新登录");
                      break;
                  case 500:
                      ElMessage.error("请求异常,请联系管理员");
                      break;
                  default:
                      ElMessage.error('请求失败');
                      break;
              }
          }
       
          // 通用方法封装
          get<T>(url: string, params?: object): Promise<ResultData<T>> {
              return this.service.get(url, { params });
          }
       
          post<T>(url: string, params?: object): Promise<ResultData<T>> {
              return this.service.post(url, params);
          }
          put<T>(url: string, params?: object): Promise<ResultData<T>> {
              return this.service.put(url, params);
          }
          delete<T>(url: string, params?: object): Promise<ResultData<T>> {
              return this.service.delete(url, { params });
          }
      }
       
      export default new Request(config)

      3. 实际使用

      src 目录下新增 api/index.ts

      • 定义请求的参数类型
      • 定义响应想具体参数类型

      这里我们使用到ts 中的 namespace ,实际开发中我们很多 api 可能会出现相同名字不同含义,所以我们使用 namespace 进行定义

      import request from "@/utils/request";
       
      namespace User {
          // login
          export interface LoginForm {
              userName: string,
              password: string
          }
      }
       
       
      export namespace System {
       
       
          export interface Info {
              path: string,
              routeName: string
          }
       
       
          export interface ResponseItem {
              code: number,
              items: Array<Sidebar>,
              success: boolean
          }
       
          export interface Sidebar {
              id: number,
              hashId: string | number,
              title: string,
              routeName: string,
              children: Array<SidebarItem>,
          }
       
          export interface SidebarItem {
              id: number,
              parentId: number,
              hashId: string | number,
              title: string,
          }
      }
       
      export const info = (params: System.Info) => {
          // response 
          if (!params || !params.path) throw new Error('Params and params in path can not empty!')
          // 这里因为是全局的一个info,根据路由地址去请求侧边栏,所需不用把地址写死
          return request.post<System.Sidebar>(params.path, { routeName: params.routeName })
      }

      Vue 文件中调用

      <script lang="ts" setup name="Sidebar">
      import { ref, reactive, onBeforeMount } from "vue"
      import { info } from "@/api"
      import { useRoute } from "vue-router"
      const route = useRoute();
       
      let loading = ref<boolean>(false);
      let sidebar = ref<any>({});
       
      const _fetch = async (): Promise<void> => {
          const routeName = route.name as string;
          const path = '/' + routeName.replace(routeName[0], routeName[0].toLocaleLowerCase()) + 'Info'
          try {
              loading.value = true;
              const res = await info({ path, routeName });
              if (!res || !res.data) return;
              sidebar.value = res.data;
          } finally {
              loading.value = false
          }
      }
       
      onBeforeMount(() => {
          _fetch();
      })
       
      </script>

      二、 mock.js 的依赖安装与处理  

      1. 安装依赖

      # 安装
      npm install mockjs --save

        在 ts 中使用时,我们需要现在 shims-vue.d.ts 文件中去抛出模块,不然会出现引入报错的问题

      /* eslint-disable */
      declare module '*.vue' {
        import type { DefineComponent } from 'vue'
        const component: DefineComponent<{}, {}, any>
        export default component
      }
       
      declare module 'mockjs';

      2. 新建 mock 所需的文件

      vue3和ts封装axios以及使用mock.js详解

       index.ts(属于mockjs全局配置文件),mockjs/javaScript/index.ts(具体的数据文件),这两个需要关注,别的不用关注

      1. 新建 mockjs/javaScript/index.ts(具体的数据文件) 

      因为我这里的数据主要是 侧边栏的数据,都是固定好的,所以并没有用到 mockjs 的规则生成数据

      import { GlobalSidebar, Sidebar } from "../../sidebar";
       
      namespace InfoSidebar {
          export type InfoSidebarParams = {
              body: string,
              type: string,
              url: string
          }
      }
       
      const dataSource: Array<GlobalSidebar> = [
          {
              mainTitle: 'JavaScript基础问题梳理',
              mainSidebar: [
                  {
                      id: 0,
                      hashId: 'This',
                      title: 'this指向',
                      routeName: 'JsBasic',
                      children: [
                          {
                              id: 1,
                              parentId: 0,
                              hashId: 'GlobalFunction',
                              title: '全局函数'
                          },
                          {
                              id: 2,
                              parentId: 0,
                              hashId: 'ObjectMethod',
                              title: '对象方法'
                          },
                          {
                              id: 3,
                              parentId: 0,
                              hashId: 'Constructor',
                              title: '构造函数'
                          },
                          {
                              id: 4,
                              parentId: 0,
                              hashId: 'SetTimeout',
                              title: '定时器、回调函数'
                          },
                          {
                              id: 5,
                              parentId: 0,
                              hashId: 'EventFunction',
                              title: '事件函数'
                          },
                          {
                              id: 6,
                              parentId: 0,
                              hashId: 'ArrowFunction',
                              title: '箭头函数'
                          },
                          {
                              id: 7,
                              parentId: 0,
                              hashId: 'CallApplyBind',
                              title: 'call、apply、bind'
                          },
                      ]
                  },
                  {
                      id: 2,
                      hashId: 'DeepClone',
                      title: '深拷贝和浅拷贝',
                      routeName: 'JsBasic',
                      children: []
                  }
              ]
          },
      ];
       
      export default {
          name: 'jsBasicInfo',
          jsBasicInfo(params: InfoSidebar.InfoSidebarParams) {
              const param = JSON.parse(params.body)
              if (!param) throw new Error("Params can not empty!");
              const data = dataSource.find((t: GlobalSidebar) => {
                  return t.mainSidebar.filter((x: Sidebar) => {
                      return x.routeName === param.routeName
                  })
              })
              return {
                  data,
                  success: true,
                  code: 200
              }
          }
      } 

      Sidebar.ts

      /**
       * @param { number } id Unique value
       * @param { string } hashId href Unique value
       * @param { string } title show current title
       * @param { string } routeName page find data
       */
       
      interface GlobalSidebar {
          mainTitle: string,
          mainSidebar: Array<Sidebar>
      }
       
      interface Sidebar {
          id: number,
          hashId: string | number,
          title: string,
          routeName: string,
          children: Array<SidebarItem>,
      }
       
      interface SidebarItem {
          id: number,
          parentId: number,
          hashId: string | number,
          title: string,
      }
       
      export {
          GlobalSidebar,
          Sidebar,
          SidebarItem
      }

      2. 新建 mockjs/index.ts 

      import Mock from "mockjs";
      import jsBasicInfo from "./tpl/javaScript/index";
      const requestMethod = 'post';
      const BASE_URL = process.env.VUE_APP_BASE_API;
      const mocks = [jsBasicInfo];
       
      for (let i of mocks) {
          Mock.mock(BASE_URL + '/' + i.name, requestMethod, i.jsBasicInfo);
      }
       
      export default Mock

      3. main.ts 引入

      import { createApp } from 'vue'
      import App from './App.vue'
       
      if(process.env.NODE_ENV == 'development'){
          require('./mockjs/index')
      }
       
      const app = createApp(App);
      app.mount('#app');

      三、结合使用

      实际上就是刚刚调用axios 的那一段代码

      <script lang="ts" setup name="Sidebar">
      import { ref, reactive, onBeforeMount } from "vue"
      import { info } from "@/api"
      import { useRoute } from "vue-router"
      const route = useRoute();
       
      let loading = ref<boolean>(false);
      let sidebar = ref<any>({});
       
      const _fetch = async (): Promise<void> => {
          const routeName = route.name as string;
          const path = '/' + routeName.replace(routeName[0], routeName[0].toLocaleLowerCase()) + 'Info'
          try {
              loading.value = true;
              const res = await info({ path, routeName });
              if (!res || !res.data) return;
              sidebar.value = res.data;
          } finally {
              loading.value = false
          }
      }
       
      onBeforeMount(() => {
          _fetch();
      })
       
      </script>

      总结

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。