Vue3 中 Axios 深度整合指南:从基础到高级实践引言

发布于:2025-05-26 ⋅ 阅读:(19) ⋅ 点赞:(0)

在现代前端开发中,与后端API的交互是构建动态应用的核心环节。Axios作为最流行的HTTP客户端之一,以其简洁的API和强大的功能在前端生态中占据重要地位。本文将全面探讨如何在Vue3项目中高效整合Axios,从基础配置到高级封装,从性能优化到安全实践,帮助开发者构建健壮的前后端交互层。

一、Axios基础与Vue3集成

1.1 Axios核心优势

  • 基于Promise:支持异步/await语法

  • 浏览器和Node.js通用:同构应用开发

  • 请求/响应拦截:全局处理逻辑

  • 自动转换JSON:简化数据处理

  • 取消请求:优化用户体验

  • CSRF/XSRF防护:内置安全特性

1.2 安装与基础使用

npm install axios
# 或
yarn add axios

基础示例:

import axios from 'axios';

// GET请求
axios.get('/api/user/123')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

// POST请求
axios.post('/api/user', { name: '张三' })
  .then(response => console.log('创建成功'));

1.3 Vue3中的集成方式

全局挂载(不推荐)
// main.js
import { createApp } from 'vue';
import axios from 'axios';

const app = createApp(App);
app.config.globalProperties.$axios = axios;
app.mount('#app');

// 组件中使用
export default {
  mounted() {
    this.$axios.get('/api/data');
  }
}

组合式API推荐方式

// src/utils/http.js
import axios from 'axios';

const instance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
});

export default instance;

// 组件中使用
import http from '@/utils/http';

export default {
  setup() {
    const fetchData = async () => {
      try {
        const { data } = await http.get('/endpoint');
        console.log(data);
      } catch (error) {
        console.error('请求失败:', error);
      }
    };
    
    return { fetchData };
  }
}

二、高级配置与封装
2.1 创建可配置的Axios实例

// src/utils/http.js
import axios from 'axios';
import { ElMessage } from 'element-plus'; // 示例使用Element Plus消息组件

const http = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
});

// 请求拦截器
http.interceptors.request.use(
  config => {
    // 添加认证token
    const token = localStorage.getItem('auth_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 响应拦截器
http.interceptors.response.use(
  response => {
    // 处理业务逻辑错误
    if (response.data?.code !== 200) {
      ElMessage.error(response.data?.message || '业务错误');
      return Promise.reject(response.data);
    }
    return response.data;
  },
  error => {
    // 统一错误处理
    if (error.response) {
      switch (error.response.status) {
        case 401:
          ElMessage.error('未授权,请重新登录');
          router.push('/login');
          break;
        case 403:
          ElMessage.error('拒绝访问');
          break;
        case 500:
          ElMessage.error('服务器错误');
          break;
        default:
          ElMessage.error(`请求错误: ${error.message}`);
      }
    } else {
      ElMessage.error('网络错误,请检查连接');
    }
    return Promise.reject(error);
  }
);

export default http;

2.2 TypeScript支持

// src/types/api.d.ts
declare module 'axios' {
  interface AxiosResponse<T = any> {
    code: number;
    message: string;
    data: T;
    // 其他自定义字段
  }
}

// 封装响应类型
export interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
  timestamp: number;
}

// 使用示例
interface User {
  id: number;
  name: string;
  email: string;
}

export const getUserInfo = (userId: number): Promise<ApiResponse<User>> => {
  return http.get(`/user/${userId}`);
};

2.3 API模块化组织

src/
  api/
    modules/
      auth.ts    # 认证相关API
      user.ts    # 用户相关API
      product.ts # 产品相关API
    index.ts     # API聚合导出
// src/api/modules/user.ts
import http from '@/utils/http';
import type { ApiResponse } from '@/types/api';

export interface UserProfile {
  id: number;
  name: string;
  avatar: string;
  // 其他字段
}

export const fetchUserProfile = (): Promise<ApiResponse<UserProfile>> => {
  return http.get('/user/profile');
};

export const updateUserProfile = (
  data: Partial<UserProfile>
): Promise<ApiResponse<UserProfile>> => {
  return http.patch('/user/profile', data);
};

三、高级特性实战
3.1 取消请求

import axios, { CancelTokenSource } from 'axios';

let cancelTokenSource: CancelTokenSource;

export const searchProducts = (keyword: string) => {
  // 取消之前的请求
  if (cancelTokenSource) {
    cancelTokenSource.cancel('取消重复请求');
  }
  
  cancelTokenSource = axios.CancelToken.source();
  
  return http.get('/products/search', {
    params: { q: keyword },
    cancelToken: cancelTokenSource.token
  });
};

// 组件中使用
const search = async () => {
  try {
    const { data } = await searchProducts(keyword.value);
    products.value = data;
  } catch (error) {
    if (!axios.isCancel(error)) {
      console.error('搜索失败:', error);
    }
  }
};

3.2 文件上传与进度监控

<template>
  <input type="file" @change="handleFileUpload" />
  <div v-if="uploadProgress > 0">
    上传进度: {{ uploadProgress }}%
  </div>
</template>

<script setup>
import { ref } from 'vue';
import http from '@/utils/http';

const uploadProgress = ref(0);

const handleFileUpload = async (event) => {
  const file = event.target.files[0];
  if (!file) return;
  
  const formData = new FormData();
  formData.append('file', file);
  
  try {
    const { data } = await http.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      onUploadProgress: (progressEvent) => {
        if (progressEvent.total) {
          uploadProgress.value = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
        }
      }
    });
    console.log('上传成功:', data);
  } catch (error) {
    console.error('上传失败:', error);
  } finally {
    setTimeout(() => {
      uploadProgress.value = 0;
    }, 2000);
  }
};
</script>

3.3 请求重试机制

// src/utils/http-retry.ts
import http from './http';

export const httpWithRetry = async (
  config: any,
  maxRetries = 3,
  retryDelay = 1000
) => {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await http(config);
      return response;
    } catch (error) {
      lastError = error;
      // 只对网络错误和5xx错误重试
      if (!error.response || error.response.status >= 500) {
        if (i < maxRetries - 1) {
          await new Promise(resolve => setTimeout(resolve, retryDelay));
          retryDelay *= 2; // 指数退避
        }
      } else {
        break;
      }
    }
  }
  
  throw lastError;
};

// 使用示例
import { httpWithRetry } from '@/utils/http-retry';

const fetchImportantData = async () => {
  try {
    const data = await httpWithRetry({
      url: '/critical-data',
      method: 'get'
    }, 5); // 最多重试5次
    console.log('获取关键数据成功:', data);
  } catch (error) {
    console.error('最终获取失败:', error);
  }
};

四、性能优化与安全

4.1 请求缓存策略

// src/utils/http-cache.ts
import http from './http';

const cache = new Map();

export const httpWithCache = async (config: any, cacheKey: string, ttl = 60000) => {
  const now = Date.now();
  
  // 检查缓存
  if (cache.has(cacheKey)) {
    const { data, timestamp } = cache.get(cacheKey);
    if (now - timestamp < ttl) {
      return data;
    }
  }
  
  // 发起新请求
  try {
    const response = await http(config);
    cache.set(cacheKey, {
      data: response,
      timestamp: now
    });
    return response;
  } catch (error) {
    // 失败时返回缓存数据(如果有)
    if (cache.has(cacheKey)) {
      console.warn('使用缓存数据:', cacheKey);
      return cache.get(cacheKey).data;
    }
    throw error;
  }
};

// 使用示例
const fetchProductList = async () => {
  return httpWithCache(
    { url: '/products', method: 'get' },
    'product-list',
    300000 // 5分钟缓存
  );
};

4.2 并发请求优化

// 使用axios.all处理并发请求
const fetchDashboardData = async () => {
  try {
    const [userRes, ordersRes, statsRes] = await Promise.all([
      http.get('/user/profile'),
      http.get('/orders/latest'),
      http.get('/dashboard/stats')
    ]);
    
    return {
      user: userRes.data,
      orders: ordersRes.data,
      stats: statsRes.data
    };
  } catch (error) {
    console.error('获取仪表盘数据失败:', error);
    throw error;
  }
};

4.3 安全最佳实践

6.2 推荐架构

  1. CSRF防护
     

    // Axios全局配置
    http.defaults.xsrfCookieName = 'csrftoken';
    http.defaults.xsrfHeaderName = 'X-CSRFToken';

  2. JWT自动刷新
     

    // 响应拦截器中处理token刷新
    let isRefreshing = false;
    let refreshSubscribers: any[] = [];
    
    http.interceptors.response.use(
      response => response,
      async error => {
        const { config, response } = error;
        
        if (response.status === 401 && !config._retry) {
          if (isRefreshing) {
            return new Promise(resolve => {
              refreshSubscribers.push((token: string) => {
                config.headers.Authorization = `Bearer ${token}`;
                resolve(http(config));
              });
            });
          }
          
          config._retry = true;
          isRefreshing = true;
          
          try {
            const { data } = await http.post('/auth/refresh');
            localStorage.setItem('auth_token', data.token);
            
            // 重试所有挂起的请求
            refreshSubscribers.forEach(cb => cb(data.token));
            refreshSubscribers = [];
            
            // 重试原始请求
            config.headers.Authorization = `Bearer ${data.token}`;
            return http(config);
          } catch (refreshError) {
            // 刷新失败,跳转登录
            localStorage.removeItem('auth_token');
            router.push('/login');
            return Promise.reject(refreshError);
          } finally {
            isRefreshing = false;
          }
        }
        
        return Promise.reject(error);
      }
    );

    五、测试与调试

    5.1 Mock服务配置
     

    // 使用msw(Mock Service Worker)
    // src/mocks/handlers.js
    import { rest } from 'msw';
    
    export const handlers = [
      rest.get('/api/user', (req, res, ctx) => {
        return res(
          ctx.delay(150),
          ctx.json({
            id: 1,
            name: 'Mock User'
          })
        );
      }),
      // 其他mock接口
    ];
    
    // src/mocks/browser.js
    import { setupWorker } from 'msw';
    import { handlers } from './handlers';
    
    export const worker = setupWorker(...handlers);

    5.2 单元测试示例

    // tests/unit/api.spec.ts
    import axios from 'axios';
    import MockAdapter from 'axios-mock-adapter';
    import { fetchUserProfile } from '@/api/modules/user';
    
    describe('User API', () => {
      let mockAxios: MockAdapter;
      
      beforeEach(() => {
        mockAxios = new MockAdapter(axios);
      });
      
      afterEach(() => {
        mockAxios.restore();
      });
      
      it('fetchUserProfile returns user data', async () => {
        const mockData = { id: 1, name: 'Test User' };
        mockAxios.onGet('/user/profile').reply(200, {
          code: 200,
          data: mockData,
          message: 'success'
        });
        
        const response = await fetchUserProfile();
        expect(response.data).toEqual(mockData);
      });
      
      it('handles 404 error', async () => {
        mockAxios.onGet('/user/profile').reply(404);
        
        await expect(fetchUserProfile()).rejects.toThrow();
      });
    });

    六、总结与最佳实践

    6.1 关键点总结

  3. 封装优于直接使用:创建配置好的Axios实例并统一管理

  4. 类型安全:为API响应添加TypeScript类型定义

  5. 模块化组织:按业务领域组织API模块

  6. 错误处理:全局拦截器中统一处理错误

  7. 安全考虑:实现CSRF防护和JWT自动刷新

  8. 性能优化:合理使用缓存、取消重复请求
     

    src/
      api/
        modules/       # 按业务模块组织的API
        types/         # 类型定义
        utils/
          http.ts      # Axios实例配置
          interceptors # 拦截器
          cache.ts     # 缓存策略
          retry.ts     # 重试机制
      stores/          # Pinia状态管理
        modules/       # 与API模块对应的状态模块


网站公告

今日签到

点亮在社区的每一天
去签到