天天看点

前端框架搭建(五)封装请求接口【vite】

1.创建

service

目录——存放接口相关文件

创建目录结构如下

前端框架搭建(五)封装请求接口【vite】
  • controller

    :前端直接与后端通信的层
  • dto

    :后端传给前端的
  • vo

    :前端传给后端的
  • request

    :封装请求接口的

2.安装axios依赖

使用 NPM:

npm install axios

           

使用 Yarn:

yarn axios
           

使用 PNPM:

pnpm i aixos
           
前端框架搭建(五)封装请求接口【vite】

安装成功

前端框架搭建(五)封装请求接口【vite】

3.封装axios请求类

service

目录下创建

instance.ts

文件,创建一个axios请求类

export default class AxiosInstance {
  

}
           

创建类型

前端框架搭建(五)封装请求接口【vite】
/** 请求的相关类型 */
declare namespace Service {
    /**
     * 请求的错误类型:
     * - axios: axios错误:网络错误, 请求超时, 默认的兜底错误
     * - http: 请求成功,响应的http状态码非200的错误
     * - backend: 请求成功,响应的http状态码为200,由后端定义的业务错误
     */
    type RequestErrorType = 'axios' | 'http' | 'backend';
  
    /** 请求错误 */
    interface RequestError {
      /** 请求服务的错误类型 */
      type: RequestErrorType;
      /** 错误码 */
      code: string | number;
      /** 错误信息 */
      msg: string;
    }
  
    /** 后端接口返回的数据结构配置 */
    interface BackendResultConfig {
      /** 表示后端请求状态码的属性字段 */
      codeKey: string;
      /** 表示后端请求数据的属性字段 */
      dataKey: string;
      /** 表示后端消息的属性字段 */
      msgKey: string;
      /** 后端业务上定义的成功请求的状态 */
      successCode: number | string;
    }
  }
           

定义属性

instance: AxiosInstance;

    backendConfig: Service.BackendResultConfig;
           

定义构造方法

/**
   *
   * @param axiosConfig - axios配置
   * @param backendConfig - 后端返回的数据配置
   */
  constructor(
    axiosConfig: AxiosRequestConfig,
    backendConfig: Service.BackendResultConfig = {
      codeKey: 'code',
      dataKey: 'data',
      msgKey: 'message',
      successCode: 200
    }
  ) {
    this.backendConfig = backendConfig;
    this.instance = axios.create(axiosConfig);
  }
           

4.设置请求拦截器

搭建整体架构

/** 设置请求拦截器 */
    setInterceptor(){
      // 拦截请求
      this.instance.interceptors.request.use(
  
      )
      // 拦截响应
      this.instance.interceptors.response.use(

      );
    }
           

①拦截请求

处理正常请求

在拦截请求处可添加对

token

或者数据的转换
async (config:AxiosRequestConfig<any>) => {
      
          const handleConfig = {...config};
          if (handleConfig.headers) {
            // 数据转换

            // 可在此处添加设置token
          }
       },
           

添加基本配置config文件

放在utils文件目录下
/** 请求超时时间 */
export const REQUEST_TIMEOUT = 60 * 1000;

/** 错误信息的显示时间 */
export const ERROR_MSG_DURATION = 3 * 1000;

/** 默认的请求错误code */
export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT';

/** 默认的请求错误文本 */
export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~';

/** 请求超时的错误code(为固定值:ECONNABORTED) */
export const REQUEST_TIMEOUT_CODE = 'ECONNABORTED';

/** 请求超时的错误文本 */
export const REQUEST_TIMEOUT_MSG = '请求超时~';

/** 网络不可用的code */
export const NETWORK_ERROR_CODE = 'NETWORK_ERROR';

/** 网络不可用的错误文本 */
export const NETWORK_ERROR_MSG = '网络不可用~';

/** 请求不成功各种状态的错误 */
export const ERROR_STATUS = {
  400: '400: 请求出现语法错误~',
  401: '401: 用户未授权~',
  403: '403: 服务器拒绝访问~',
  404: '404: 请求的资源不存在~',
  405: '405: 请求方法未允许~',
  408: '408: 网络请求超时~',
  500: '500: 服务器内部错误~',
  501: '501: 服务器未实现请求功能~',
  502: '502: 错误网关~',
  503: '503: 服务不可用~',
  504: '504: 网关超时~',
  505: '505: http版本不支持该请求~',
  [DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG
};

/** 不弹出错误信息的code */
export const NO_ERROR_MSG_CODE: (string | number)[] = [];

/** token失效需要刷新token的code(这里的66666只是个例子,需要将后端表示token过期的code填进来) */
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];
           

统一处理请求异常

(axiosError: AxiosError) => {
          console.log('axiosError',axiosError)
      }
           

②拦截响应

// 拦截响应
      this.instance.interceptors.response.use(
        (response:AxiosResponse<any, any>)=>{
          // 网络返回码
          const { status } = response;
          if (status === 200 || status < 300 || status === 304) {
            const backend = response.data;
            const { codeKey, dataKey, successCode } = this.backendConfig;
            // 请求成功
            if (backend[codeKey] === successCode) {
              return backend[dataKey];
            }

          }

        }
      );
           

5.添加创建请求接口

添加请求接口类型

添加请求参数类型

interface RequestParam {
    url: string;
    method?: RequestMethod;
    data?: any;
    axiosConfig?: AxiosRequestConfig;
}
           

创建请求

/**
 * 创建请求
 * @param axiosConfig - axios配置
 * @param backendConfig - 后端接口字段配置
 */
export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
    const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);

  /**
   * 异步promise请求
   * @param param - 请求参数
   * - url: 请求地址
   * - method: 请求方法(默认get)
   * - data: 请求的body的data
   * - axiosConfig: axios配置
   */
  async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> {
    const { url } = param;
    const method = param.method || 'get';
    const { instance } = customInstance;
    const res = await getRequestResponse({
      instance,
      method,
      url,
      data: param.data,
      config: param.axiosConfig
    });

    return res;
  }

}

async function getRequestResponse(params: {
    instance: AxiosInstance;
    method: RequestMethod;
    url: string;
    data?: any;
    config?: AxiosRequestConfig;
  }) {
    const { instance, method, url, data, config } = params;
  
    let res: any;
    if (method === 'get' || method === 'delete') {
      res = await instance[method](url, config);
    } else {
      res = await instance[method](url, data, config);
    }
    return res;
  }
           

添加请求方法(get、post等)

/**
   * get请求
   * @param url - 请求地址
   * @param config - axios配置
   */
  function get<T>(url: string, config?: AxiosRequestConfig) {
    return asyncRequest<T>({ url, method: 'get', axiosConfig: config });
  }

  /**
   * post请求
   * @param url - 请求地址
   * @param data - 请求的body的data
   * @param config - axios配置
   */
  function post<T>(url: string, data?: any, config?: AxiosRequestConfig) {
    return asyncRequest<T>({ url, method: 'post', data, axiosConfig: config });
  }
  /**
   * put请求
   * @param url - 请求地址
   * @param data - 请求的body的data
   * @param config - axios配置
   */
  function put<T>(url: string, data?: any, config?: AxiosRequestConfig) {
    return asyncRequest<T>({ url, method: 'put', data, axiosConfig: config });
  }

  /**
   * delete请求
   * @param url - 请求地址
   * @param config - axios配置
   */
  function handleDelete<T>(url: string, config: AxiosRequestConfig) {
    return asyncRequest<T>({ url, method: 'delete', axiosConfig: config });
  }

  return {
    get,
    post,
    put,
    delete: handleDelete
  };
           

6.创建请求接口

创建环境配置

在根目录下创建

.env-config.ts

对请求服务进行配置

/** 请求服务的环境配置 */
type ServiceEnv = Record<ServiceEnvType, ServiceEnvConfig>;

/** 不同请求服务的环境配置 */
const serviceEnv: ServiceEnv = {
  dev: {
    url: 'http://localhost:8080',
    urlPattern: '/url-pattern',
    secondUrl: 'http://localhost:8081',
    secondUrlPattern: '/second-url-pattern'
  },
  test: {
    url: 'http://localhost:8080',
    urlPattern: '/url-pattern',
    secondUrl: 'http://localhost:8081',
    secondUrlPattern: '/second-url-pattern'
  },
  prod: {
    url: 'http://localhost:8080',
    urlPattern: '/url-pattern',
    secondUrl: 'http://localhost:8081',
    secondUrlPattern: '/second-url-pattern'
  }
};

/**
 * 获取当前环境模式下的请求服务的配置
 * @param env 环境
 */
export function getServiceEnvConfig(env: ImportMetaEnv) {
  const { VITE_SERVICE_ENV = 'dev' } = env;

  const config = serviceEnv[VITE_SERVICE_ENV];

  return config;
}

           

设置对应的类型

在typing中创建

.env.d.ts

/**
 *后台服务的环境类型
 * - dev: 后台开发环境
 * - test: 后台测试环境
 * - prod: 后台生产环境
 */
 type ServiceEnvType = 'dev' | 'test' | 'prod';

 /** 后台服务的环境配置 */
 interface ServiceEnvConfig {
   /** 请求地址 */
   url: string;
   /** 匹配路径的正则字符串, 用于拦截地址转发代理(任意以 /开头 + 字符串, 单个/不起作用) */
   urlPattern: '/url-pattern';
   /** 另一个后端请求地址(有多个不同的后端服务时) */
   secondUrl: string;
   /** 匹配路径的正则字符串, 用于拦截地址转发代理(任意以 /开头 + 字符串, 单个/不起作用) */
   secondUrlPattern: '/second-url-pattern';
 }
 
 interface ImportMetaEnv {
   /** 项目基本地址 */
   readonly VITE_BASE_URL: string;
   /** 项目名称 */
   readonly VITE_APP_NAME: string;
   /** 项目标题 */
   readonly VITE_APP_TITLE: string;
   /** 项目描述 */
   readonly VITE_APP_DESC: string;
   /** iconify图标作为组件的前缀 */
   readonly VITE_ICON_PREFFIX: string;
   /**
    * 本地SVG图标作为组件的前缀, 请注意一定要包含 VITE_ICON_PREFFIX
    * - 格式 {VITE_ICON_PREFFIX}-{本地图标集合名称}
    * - 例如:icon-local
    */
   readonly VITE_ICON_LOCAL_PREFFIX: string;
   /** 后端服务的环境类型 */
   readonly VITE_SERVICE_ENV?: ServiceEnvType;
   /** hash路由模式 */
   readonly VITE_HASH_ROUTE?: 'Y' | 'N';
   /** 是否是部署的vercel */
   readonly VITE_VERCEL?: 'Y' | 'N';
 }
 
 interface ImportMeta {
   readonly env: ImportMetaEnv;
 }
 
           

创建接口

import { getServiceEnvConfig } from '~/.env-config';
import { createRequest } from "./request";

const { url } = getServiceEnvConfig(import.meta.env);


export const web = createRequest({baseURL: url})
           

7.测试登录接口

创建user子目录

import { web } from "../../request";

export function login(){
    return web.post<String>('/user/login')
}
           

统一导出

使用

<script setup lang="ts">


const reuqest = () => userRequest.login()
</script>

<template>
  <h1 class="text-25px text-#ff6700 bg-#ccc">{{ msg }}</h1>

  <button type="button" @click="reuqest">请求接口</button>


  <div class="card"> 
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>

           

完成~

前端框架搭建(五)封装请求接口【vite】
前端框架搭建(五)封装请求接口【vite】

继续阅读