Vue3中使用Axios指南

Vue3中使用Axios完整指南

目录

简介

Axios是一个基于Promise的HTTP客户端,用于浏览器和Node.js中发送HTTP请求。在Vue3项目中,Axios是最常用的HTTP请求库之一,它提供了简洁的API和强大的功能,包括请求/响应拦截、请求取消、自动数据转换等。

本指南将从零开始,详细介绍如何在Vue3项目中使用Axios,包括基础配置、请求封装、错误处理等各个方面。

安装与配置

安装Axios

首先,需要在项目中安装Axios:

npm install axios
# 或
yarn add axios
# 或
pnpm add axios

基础配置

创建一个Axios实例,并进行基础配置:

// src/utils/request.ts
import axios from 'axios'
import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'

// 扩展Axios的类型定义
declare module 'axios' {
  interface InternalAxiosRequestConfig {
    metadata?: {
      startTime: Date
    }
  }
}

// 创建axios实例
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'https://api.example.com', // API基础路径
  timeout: 15000, // 请求超时时间
  headers: {
    'Content-Type': 'application/json;charset=UTF-8' // 默认内容类型
  },
  // 新版本Axios支持的新选项
  transitional: {
    silentJSONParsing: false, // 如果JSON解析失败,抛出错误而不是静默处理
    forcedJSONParsing: true  // 尝试解析非JSON类型的响应
  },
  // 启用请求/响应转换器
  transformResponse: [function (data) {
    try {
      return JSON.parse(data)
    } catch (err) {
      return data
    }
  }]
})

export default service

Axios基础使用

直接使用Axios

在Vue3组件中,可以直接使用Axios发送请求:

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'

const userData = ref(null)
const loading = ref(false)

const fetchData = async () => {
  loading.value = true
  try {
    // 直接使用axios发送GET请求
    const response = await axios.get('https://jsonplaceholder.typicode.com/users/1')
    userData.value = response.data
  } catch (error) {
    console.error('获取用户数据失败:', error)
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <div>
    <button @click="fetchData">获取用户数据</button>
    <div v-if="loading">加载中...</div>
    <div v-else>
      <pre>{{ userData }}</pre>
    </div>
  </div>
</template>

封装Axios实例

为了更好的管理和复用,推荐将Axios实例封装成一个工具函数:

// src/utils/request.ts
// ... 前面的配置代码 ...

// 导出axios实例
export default service

然后在组件中使用封装好的实例:

<script setup lang="ts">
import { ref } from 'vue'
import request from '../utils/request'

const userData = ref(null)
const loading = ref(false)

const fetchData = async () => {
  loading.value = true
  try {
    const response = await request.get('/users/1')
    userData.value = response.data
  } catch (error) {
    console.error('获取用户数据失败:', error)
  } finally {
    loading.value = false
  }
}
</script>

请求与响应拦截器

请求拦截器

请求拦截器可以在请求发送前对请求进行统一处理,如添加token、设置请求头等:

// src/utils/request.ts
// ... 前面的配置代码 ...

// 请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 添加认证token
    const token = localStorage.getItem('token')
    if (token && config.headers) {
      config.headers['Authorization'] = `Bearer ${token}`
    }

    // 添加请求元数据(用于计算请求耗时)
    config.metadata = { startTime: new Date() }

    return config
  },
  (error: AxiosError) => {
    // 对请求错误做些什么
    console.error('请求错误:', error)
    return Promise.reject(error)
  }
)

响应拦截器

响应拦截器可以在接收到响应后对响应数据进行统一处理,如统一错误处理、数据格式化等:

// src/utils/request.ts
// ... 前面的配置代码 ...

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    // 计算请求耗时
    if (response.config.metadata) {
      const endTime = new Date()
      const duration = endTime.getTime() - response.config.metadata.startTime.getTime()
      console.log(`请求耗时: ${duration}ms`)
    }

    const res = response.data

    // 根据业务状态码进行处理
    if (res.code !== 200) {
      // 处理错误情况
      console.error('响应错误:', res.message)

      // 特殊状态码处理,如401表示未登录
      if (res.code === 401) {
        // 清除token
        localStorage.removeItem('token')
        // 可以添加跳转逻辑
        console.log('未登录,请先登录')
      }
      return Promise.reject(new Error(res.message || '请求失败'))
    } else {
      return res
    }
  },
  (error: AxiosError) => {
    // 对响应错误做点什么
    console.error('响应错误:', error)

    // 处理网络错误
    if (!error.response) {
      console.error('网络错误,请检查网络连接')
      return Promise.reject(new Error('网络错误,请检查网络连接'))
    }

    // 处理HTTP错误状态码
    const { status } = error.response
    switch (status) {
      case 400:
        console.error('请求参数错误')
        break
      case 401:
        console.error('未授权,请登录')
        localStorage.removeItem('token')
        break
      case 403:
        console.error('拒绝访问')
        break
      case 404:
        console.error('请求地址出错')
        break
      case 408:
        console.error('请求超时')
        break
      case 500:
        console.error('服务器内部错误')
        break
      default:
        console.error(`请求失败,状态码: ${status}`)
    }

    return Promise.reject(error)
  }
)

API模块化组织

为了更好的组织和管理API接口,推荐采用模块化的方式组织API:

1. 定义类型

首先,定义API相关的类型:

// src/api/types.ts
// 定义通用响应类型
export interface ResponseData<T = any> {
  code: number
  message: string
  data: T
}

// 用户相关类型
export interface UserInfo {
  id: number
  name: string
  email: string
  phone?: string
  website?: string
  avatar?: string
  role?: string
  createTime?: string
  updateTime?: string
}

export interface LoginForm {
  username: string
  password: string
}

// 文章相关类型
export interface Article {
  id: number
  title: string
  content: string
  category?: string
  author?: string
  createTime?: string
  updateTime?: string
  tags?: string[]
  viewCount?: number
}

2. 创建API模块

按功能模块创建API文件,如用户API、文章API等:

// src/api/modules/user.ts
import service from '../../utils/request'
import type { ResponseData, UserInfo, LoginForm, RegisterForm } from '../types'

// 用户相关接口
export const userApi = {
  // 获取用户信息
  getUserInfo(id: string): Promise<any> {
    return service({
      url: '/users/' + id,
      method: 'get'
    })
  },

  // 用户登录
  login(data: LoginForm): Promise<any> {
    return service({
      url: '/login',
      method: 'post',
      data
    })
  },

  // 用户注册
  register(userInfo: RegisterForm): Promise<ResponseData> {
    return service({
      url: '/register',
      method: 'post',
      data: userInfo
    })
  },

  // 更新用户信息
  updateUser(id: string, userInfo: Partial<UserInfo>): Promise<ResponseData> {
    return service({
      url: '/user/' + id,
      method: 'put',
      data: userInfo
    })
  },

  // 获取用户列表
  getUserList(params?: { page?: number; pageSize?: number; keyword?: string }): Promise<ResponseData<UserInfo[]>> {
    return service({
      url: '/user/list',
      method: 'get',
      params
    })
  }
}

3. 统一导出API

创建一个统一的导出文件,方便引入:

// src/api/index.ts
// 导出所有类型
export * from './types'

// 导入各个API模块
import { userApi } from './modules/user'
import { articleApi } from './modules/article'
import { uploadApi } from './modules/upload'
import { commonApi } from './modules/common'

// 统一导出所有API模块
export {
  userApi,
  articleApi,
  uploadApi,
  commonApi
}

4. 在组件中使用API

在组件中,可以方便地使用定义好的API:

<script setup lang="ts">
import { ref } from 'vue'
import { userApi } from '../api'

const userData = ref(null)
const loading = ref(false)

const fetchUserData = async () => {
  loading.value = true
  try {
    const response = await userApi.getUserInfo('1')
    userData.value = response.data
    console.log('获取用户数据成功:', response)
  } catch (error) {
    console.error('获取用户数据失败:', error)
  } finally {
    loading.value = false
  }
}

const handleLogin = async () => {
  try {
    const response = await userApi.login({
      username: 'example',
      password: '123456'
    })
    console.log('登录成功:', response)
    // 处理登录逻辑...
  } catch (error) {
    console.error('登录失败:', error)
  }
}
</script>

<template>
  <div>
    <button @click="fetchUserData">获取用户数据</button>
    <button @click="handleLogin">登录</button>
    <div v-if="loading">加载中...</div>
    <div v-else>
      <pre>{{ userData }}</pre>
    </div>
  </div>
</template>

请求取消功能

在Vue3中,可以使用Axios的AbortController API来实现请求取消功能,这对于组件卸载时取消未完成的请求、防止重复请求等场景非常有用。

基本用法

// 创建一个控制器
const controller = new AbortController()

// 获取控制器的信号
const signal = controller.signal

// 发送请求时传入signal
axios.get('/api/data', { signal })

// 取消请求
controller.abort()

组件中使用请求取消

在Vue组件中,可以在组件卸载时自动取消请求:

<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import request from '../utils/取消请求.ts'

const userList = ref([])
const loading = ref(false)

// 创建一个控制器用于管理当前组件的请求
const controller = new AbortController()

const fetchUserList = async () => {
  try {
    loading.value = true
    userList.value = await request.get('/api/users', {
      signal: controller.signal  // 用于取消 HTTP 请求的关键配置
    })
  } catch (error: any) {
    if (error.name === 'CanceledError') {
      console.log('获取用户列表请求被取消')
    } else {
      console.error('获取用户列表失败:', error)
    }
  } finally {
    loading.value = false
  }
}

// 组件卸载时取消请求
onUnmounted(() => {
  controller.abort()
})

// 取消当前请求
const cancelRequest = () => {
  controller.abort('用户主动取消请求')
}
</script>

<template>
  <div>
    <button @click="fetchUserList">获取用户列表</button>
    <button @click="cancelRequest">取消请求</button>
    <div v-if="loading">加载中...</div>
    <ul v-else>
      <li v-for="user in userList" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  </div>
</template>

防止重复请求

在某些场景下,如搜索框输入时,需要防止用户快速连续触发多次请求,可以使用AbortController取消之前的请求:

let controller: AbortController | null = null

const search = async (keyword: string) => {
  // 取消之前的请求
  if (controller) {
    controller.abort()
  }

  // 创建新的控制器
  controller = new AbortController()

  const response = await axios.get('/api/search', {
    params: { keyword },
    signal: controller.signal
  })

  return response.data
}

错误处理

良好的错误处理机制对于构建健壮的Web应用非常重要。Axios提供了多种错误处理方式。

基本错误处理

try {
  const response = await axios.get('/api/data')
  // 处理响应数据
} catch (error) {
  // 基本错误处理
  console.error('请求失败:', error)
}

详细错误处理

Axios提供了详细的错误信息,可以根据不同的错误类型进行针对性处理:

try {
  const response = await axios.get('/api/data')
  // 处理响应数据
} catch (error) {
  if (axios.isAxiosError(error)) {
    if (error.response) {
      // 服务器返回了响应,但状态码不在2xx范围内
      console.error('服务器错误:', error.response.status, error.response.data)
    } else if (error.request) {
      // 请求已发出,但没有收到响应
      console.error('网络错误: 未收到服务器响应')
    } else {
      // 设置请求时发生了错误
      console.error('请求配置错误:', error.message)
    }
  } else {
    // 非Axios错误
    console.error('未知错误:', error)
  }
}

统一错误处理(通过响应拦截器)

在响应拦截器中统一处理错误,可以避免在每个请求中重复编写错误处理代码:

// src/utils/request.ts
service.interceptors.response.use(
  (response) => {
    // 处理成功响应
    return response.data
  },
  (error) => {
    // 统一错误处理
    if (error.response) {
      // 服务器返回了响应,但状态码不在2xx范围内
      switch (error.response.status) {
        case 400:
          console.error('请求参数错误')
          break
        case 401:
          console.error('未授权,请登录')
          // 可以在这里处理登录逻辑,如清除token并跳转到登录页
          break
        case 403:
          console.error('拒绝访问')
          break
        case 404:
          console.error('请求地址出错')
          break
        case 500:
          console.error('服务器内部错误')
          break
        default:
          console.error(`请求失败,状态码: ${error.response.status}`)
      }
    } else if (error.request) {
      // 请求已发出,但没有收到响应
      console.error('网络错误: 未收到服务器响应')
    } else {
      // 设置请求时发生了错误
      console.error('请求配置错误:', error.message)
    }

    // 返回错误信息,让调用方也能处理
    return Promise.reject(error)
  }
)

文件上传

在Vue3项目中,使用Axios上传文件非常简单。可以使用FormData对象来构建文件数据。

基本文件上传

<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'

const fileInput = ref<HTMLInputElement | null>(null)
const uploadResult = ref(null)
const uploading = ref(false)

const handleFileChange = (event: Event) => {
  // 处理文件选择逻辑...
}

const uploadFile = async () => {
  if (!fileInput.value?.files?.length) return

  uploading.value = true
  const file = fileInput.value.files[0]

  try {
    // 创建FormData对象
    const formData = new FormData()
    formData.append('file', file)

    // 使用axios发送POST请求
    const response = await axios.post('/api/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data' // 指定内容类型为multipart/form-data
      }
    })

    uploadResult.value = response.data
    console.log('文件上传成功:', response)
  } catch (error) {
    console.error('文件上传失败:', error)
  } finally {
    uploading.value = false
  }
}
</script>

<template>
  <div>
    <input type="file" @change="handleFileChange" ref="fileInput" />
    <button @click="uploadFile" :disabled="uploading">上传文件</button>
    <div v-if="uploading">上传中...</div>
    <div v-if="uploadResult">
      <h4>上传结果:</h4>
      <pre>{{ uploadResult }}</pre>
    </div>
  </div>
</template>

封装文件上传API

可以将文件上传功能封装成API模块,方便复用:

// src/api/modules/upload.ts
import service from '../../utils/request'
import type { UploadResult } from '../types'

// 文件上传相关接口
export const uploadApi = {
  // 上传头像
  uploadAvatar(file: File): Promise<ResponseData<UploadResult>> {
    const formData = new FormData()
    formData.append('file', file)

    return service({
      url: '/api/upload/avatar',
      method: 'post',
      data: formData,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  },

  // 上传图片
  uploadImage(file: File, params?: { folder?: string; compress?: boolean }): Promise<ResponseData<UploadResult>> {
    const formData = new FormData()
    formData.append('file', file)

    // 添加额外参数
    if (params) {
      Object.entries(params).forEach(([key, value]) => {
        if (value !== undefined) {
          formData.append(key, value)
        }
      })
    }

    return service({
      url: '/api/upload/image',
      method: 'post',
      data: formData,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  }
}

常见问题解决方案

1. 跨域问题

在前端开发中,跨域是一个常见问题。可以通过以下几种方式解决:

使用代理

在Vite项目中,可以通过配置vite.config.ts来解决跨域问题:

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

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://backend-api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

然后在代码中直接使用相对路径:

// 无需再配置baseURL
const service = axios.create({
  baseURL: '/api' // 使用相对路径,会被vite代理转发
})

2. 请求超时处理

处理请求超时问题,可以设置合理的超时时间,并在超时后进行重试或提示用户:

// src/utils/request.ts

// 创建axios实例,设置超时时间
const service: AxiosInstance = axios.create({
  timeout: 10000  // 10秒超时
})

// 响应拦截器中处理超时
service.interceptors.response.use(
  (response) => {
    return response.data
  },
  (error) => {
    if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
      console.error('请求超时')
      // 可以在这里实现重试逻辑或提示用户
    }
    return Promise.reject(error)
  }
)

3. 请求进度显示

对于文件上传或慢速请求,可以显示请求进度:

// 上传文件时显示进度
const uploadFileWithProgress = async (file: File) => {
  const formData = new FormData()
  formData.append('file', file)

  return axios.post('/api/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: (progressEvent) => {
      if (progressEvent.total) {
        const percentCompleted = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        )
        console.log(`上传进度: ${percentCompleted}%`)
        // 可以在这里更新UI显示上传进度
      }
    }
  })
}

5. 并发请求控制

当需要同时发送多个请求,但需要控制并发数量时,可以使用Promise.all或第三方库如p-limit:

// 使用Promise.all控制并发
import axios from 'axios'

// 假设有10个请求要发送
const requestPromises = Array.from({ length: 10 }, (_, i) => 
  axios.get(`/api/items/${i}`)
)

// 使用Promise.all发送所有请求
Promise.all(requestPromises)
  .then(responses => {
    // 所有请求都已完成
    const results = responses.map(response => response.data)
    console.log('所有请求结果:', results)
  })
  .catch(error => {
    // 至少有一个请求失败
    console.error('请求失败:', error)
  })

// 使用p-limit控制并发数量
import pLimit from 'p-limit'
import axios from 'axios'

const limit = pLimit(5)  // 最多5个并发

const requestPromises = Array.from({ length: 20 }, (_, i) => 
  limit(() => axios.get(`/api/items/${i}`))
)

Promise.all(requestPromises)
  .then(responses => {
    const results = responses.map(response => response.data)
    console.log('所有请求结果:', results)
  })
  .catch(error => {
    console.error('请求失败:', error)
  })

总结

本指南详细介绍了在Vue3项目中使用Axios的各个方面,从基础安装配置到高级功能实现,再到企业级最佳实践。通过合理地封装和使用Axios,可以构建出健壮、可维护的前端应用。

关键要点总结:

  1. 使用Axios实例封装基础配置和拦截器
  2. 采用模块化方式组织API接口
  3. 实现统一的错误处理机制
  4. 合理使用请求取消功能
  5. 处理常见的跨域、超时传等问题

希望这份指南能够帮助你在Vue3项目中更好地使用Axios,构建出高效、健壮的前端应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值