Vue3封装Axios

319 阅读5分钟

1、创建文件

  • utils文件夹下新建request.ts(或request.js)
   // 安装
   yarn add axios
   //Ts安装qs
   yarn add @types/qs 
   yarn add qs
   //Js安装qs
   yarn add qs

2、默认配置

// 环境变量获取当前URL
const { VITE_BASE_URL } = import.meta.env

// 如果遇到跨域问题到vite.config配好代理后在baseURL处修改逻辑
const baseURL = VITE_BASE_URL
const instance = axios.create({
    baseURL, // 请求地址
    timeout: 8000, // 请求超时
    withCredentials: true, // 跨域请求时发送Cookie
})
  • withCredentials 有什么用?
    跨域请求是否提供凭据信息(cookie、HTTP认证及客户端SSL证明等),也可以简单的理解为,当前请求为跨域类型时是否在请求中协带cookie。
    需要注意是,当配置了xhr.withCredentials = true时,必须在后端增加 response 头信息Access-Control-Allow-Origin,且必须指定域名,而不能指定为*。
    如果在同域下配置xhr.withCredentials,无论配置true还是false,效果都会相同,且会一直提供凭据信息(cookie、HTTP认证及客户端SSL证明等)

  • 根据postdata的类型来配置headercontent-type
    特别注意的是dataForm Data类型,由浏览器自己设定content-type,如果你自定义了,那么代码中会删除;同时,请求的时候,不要对formdata做处理

  • post提交数据方式
    axios默认添加了对请求方法'post', 'put', 'patch'添加了默认请求头'Content-Type': 'application/x-www-form-urlencoded',但是如果传参data是json对象,那么请求头就会更改为Content-Type':application/json;charset=utf-8。
    如果传参data不是json对象,这个时候我们就可以按照下面的方法,也就是transformRequest对请求数据改造,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val都进行了URL转码。大部分服务端语言都对这种方式有很好的支持,同时我们可以不设置请求头,使用默认即可。

  • application/json(推荐)
    application/json:Content-Type 告诉服务端消息主体是序列化后的JSON字符串

instance.defaults.headers.post['Content-Type'] = 'application/json'

3、请求截器

  • 在请求被 thencatch 处理前拦截它们。
  • paramsSerializer qs序列化
// 添加请求拦截器  
instance.interceptors.request.use(
    (config) => {
        // 在发送请求之前做些什么
        if (config.headers) {
            // 是否需要处理token。在此写token逻辑
            //const token = Storage.get(accessToken)
            //if (token) {
                //config.headers['x-sankuan-token'] = token
            //}
            config.headers['X-Requested-With'] = 'XMLHttpRequest'
        }
        if (config.method === 'get') {
            // 为get请求paramsSerializer序列化
            config.paramsSerializer = function (params) {
                return qs.stringify(params, { arrayFormat: 'repeat' })
            }
        }
        return config
    },
    (error) => {
        // 对请求错误做些什么
        return Promise.reject(error)
    }
)

4、响应拦截器

  • 在响应被 thencatch 处理前拦截它们。

  • 对状态码建议单独写一个enums

  • 新建文件夹enums后再新建httpEnum.ts文件

/**
 * @description: 状态码
 */
export enum StateEnum {
    /** 200 请求成功 */
    OK = 200,

    /** 201 创建成功 */
    CREATED = 201,

    /** 204 成功且不反回 body */
    NO_CONTENT = 204,

    /** 400 请求参数错误 */
    INVALID_REQUEST = 400,

    /** 401 未登录 */
    UNAUTHORIZED = 401,

    /** 403 权限不足 */
    FORBIDDEN = 403,

    /** 404 资源不存在或资源不属于该用户 */
    NOT_FOUND = 404,

    /** 500 服务器发生错误,用户将无法判断发出的请求是否成功 */
    INTERNAL_SERVER_ERROR = 500,
}
instance.interceptors.response.use(
  (response) => {
    return response.data || {}
  },
  (error) => {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    const res = error.response || {}
    const status = parseInt(res.status)
    const message = res.data?.error || '网络错误'
    switch (status) {
      case StateEnum.INVALID_REQUEST:
        alert(message)
        break
      case StateEnum.FORBIDDEN:
      case StateEnum.UNAUTHORIZED:
        alert(message)
        router.push(`/login`)
        break
      case StateEnum.NOT_FOUND:
        alert(message)
        break
      case StateEnum.INTERNAL_SERVER_ERROR:
        alert('服务器发生错误,请稍后再试!')
        break
      default:
        alert('请求超时,请检查您的网络!')
        break
    }
    return Promise.reject(error)
  }
)

5、导出

export default instance

6、最终request

import qs from 'qs'
import axios from 'axios'
import router from '@/router'
import { StateEnum } from '@/enums/httpEnum'
// 环境变量获取当前URL 
const { VITE_BASE_URL } = import.meta.env 

// 如果遇到跨域问题到vite.config配好代理后在baseURL处修改逻辑 
const baseURL = VITE_BASE_URL 
const instance = axios.create({ 
    baseURL, // 请求地址 
    timeout: 8000, // 请求超时 
    withCredentials: true, // 跨域请求时发送Cookie 
})

// 根据post的data的类型来配置header的content-type
instance.defaults.headers.post['Content-Type'] = 'application/json'

// 添加请求拦截器 
instance.interceptors.request.use( 
    (config) => { 
        // 在发送请求之前做些什么 
        if (config.headers) { 
            // 是否需要处理token。在此写token逻辑 
            //const token = Storage.get(accessToken) 
            //if (token) { 
                //config.headers['x-sankuan-token'] = token 
            //} 
            config.headers['X-Requested-With'] = 'XMLHttpRequest' 
        } 
        if (config.method === 'get') {
            // 为get请求paramsSerializer序列化 
            config.paramsSerializer = function (params) { 
                return qs.stringify(params, { arrayFormat: 'repeat' }) 
            } 
        } 
        return config 
    }, 
    (error) => { 
        // 对请求错误做些什么 
        return Promise.reject(error) 
    } 
)
// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    return response.data || {}
  },
  (error) => {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    const res = error.response || {}
    const status = parseInt(res.status)
    const message = res.data?.error || '网络错误'
    switch (status) {
      case StateEnum.INVALID_REQUEST:
        alert(message)
        break
      case StateEnum.FORBIDDEN:
      case StateEnum.UNAUTHORIZED:
        alert(message)
        router.push(`/login`)
        break
      case StateEnum.NOT_FOUND:
        alert(message)
        break
      case StateEnum.INTERNAL_SERVER_ERROR:
        alert('服务器发生错误,请稍后再试!')
        break
      default:
        alert('请求超时,请检查您的网络!')
        break
    }
    return Promise.reject(error)
  }
)

export default instance

6、使用request

  • 新建文件夹api后再新建user文件夹,再建两个ts文件index.tsmodel.d.ts
    index.ts用于封装axios请求。model.d.ts用于写请求的数据类型或获的数据类型(js忽略model.d.ts)

  • index.ts

import request from '@/utils/request'
import { LoginReqParams, LoginResResult } from './model'

export const PostLogin = (params: LoginReqParams): Promise<LoginResResult> =>
    request.post('/login', params)
    
export const GetMe = (): Promise<LoginResResult> =>
    request.get('/me')
  • model.d.ts
export interface LoginReqParams {
    device: string
    password: string
    phone_number: string
}

export interface LoginResResult {
    id: string
    is_admin: boolean
    name: string
    x_sankuan_token: string
}

7、页面调用

<script setup lang="ts">
import { PostLogin, GetMe } from '@/api/user/index'
import { LoginReqParams } from '@/api/user/model'

const formInline = reactive<LoginReqParams>({
    device: 'mobile',
    password: '',
    phone_number: '',
})
const onClick = async () => {
    try {
         // 成功的逻辑
        await PostLogin(formInline)
        const res = await GetMe()
    } catch (e) {
        // 失败的逻辑
    }
    
}

</script>

8、建议

  • 建议一个页面对应一个api下的文件夹(login页面对应api下的user,news页面对应api下的news)便于后期维护