Vue.js 接口封装最佳实践:从基础到高级

1. 为什么需要封装 Vue 接口?

在 Vue.js 项目中,直接在每个组件中调用 axios.get()fetch() 会导致以下问题:

  • 代码重复:相同的 API 基础 URL、请求头配置在每个请求中重复编写。
  • 难以维护:当后端接口地址变更或需要统一添加认证逻辑时,需要修改所有相关组件。
  • 错误处理分散:每个组件都需要单独处理网络错误、超时、权限验证等。
  • 缺乏统一拦截器:无法全局处理请求/响应,如添加 loading 状态、统一错误提示。

因此,对 HTTP 请求进行统一封装是 Vue 项目架构中必不可少的一环。

2. 基础封装:创建统一的请求实例

首先,我们使用 Axios(最流行的 HTTP 客户端)创建一个基础请求实例。

javascript 复制代码
// src/utils/request.js
import axios from 'axios';

// 创建 axios 实例
const request = axios.create({
  baseURL: process.env.VUE_APP_API_BASE_URL || '/api', // 基础 URL
  timeout: 10000, // 请求超时时间
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
});

// 请求拦截器
request.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么,例如添加 token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  response => {
    // 对响应数据做点什么
    const res = response.data;
    // 假设后端返回格式为 { code: 200, data: {}, message: 'success' }
    if (res.code === 200) {
      return res.data;
    } else {
      // 业务错误处理
      return Promise.reject(new Error(res.message || '请求失败'));
    }
  },
  error => {
    // 对响应错误做点什么
    if (error.response) {
      // 服务器返回了错误状态码
      switch (error.response.status) {
        case 401:
          // 未授权,跳转到登录页
          router.push('/login');
          break;
        case 403:
          // 权限不足
          Message.error('权限不足,请联系管理员');
          break;
        case 404:
          // 接口不存在
          Message.error('请求的资源不存在');
          break;
        case 500:
          // 服务器内部错误
          Message.error('服务器内部错误,请稍后重试');
          break;
        default:
          Message.error(error.response.data.message || '请求失败');
      }
    } else if (error.request) {
      // 请求发送了但没有收到响应
      Message.error('网络异常,请检查网络连接');
    } else {
      // 请求配置出错
      Message.error('请求配置错误');
    }
    return Promise.reject(error);
  }
);

export default request;

3. 进阶封装:按模块组织 API

将不同业务模块的 API 集中管理,提高代码可读性和维护性。

javascript 复制代码
// src/api/user.js
import request from '@/utils/request';

// 用户相关接口
export const userApi = {
  // 获取用户列表
  getUsers(params) {
    return request({
      url: '/users',
      method: 'get',
      params
    });
  },
  
  // 获取用户详情
  getUserById(id) {
    return request({
      url: `/users/${id}`,
      method: 'get'
    });
  },
  
  // 创建用户
  createUser(data) {
    return request({
      url: '/users',
      method: 'post',
      data
    });
  },
  
  // 更新用户
  updateUser(id, data) {
    return request({
      url: `/users/${id}`,
      method: 'put',
      data
    });
  },
  
  // 删除用户
  deleteUser(id) {
    return request({
      url: `/users/${id}`,
      method: 'delete'
    });
  }
};

// src/api/product.js
import request from '@/utils/request';

// 商品相关接口
export const productApi = {
  // 获取商品列表
  getProducts(params) {
    return request({
      url: '/products',
      method: 'get',
      params
    });
  },
  
  // 获取商品详情
  getProductById(id) {
    return request({
      url: `/products/${id}`,
      method: 'get'
    });
  }
};

4. 在 Vue 组件中使用封装好的 API

在 Vue 组件中,我们可以优雅地调用封装好的 API。

vue 复制代码
<template>
  <div>
    <h1>用户列表</h1>
    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
    <button @click="loadUsers">加载用户</button>
  </div>
</template>

<script>
import { userApi } from '@/api/user';

export default {
  name: 'UserList',
  data() {
    return {
      users: []
    };
  },
  methods: {
    async loadUsers() {
      try {
        // 调用封装好的 API
        this.users = await userApi.getUsers({
          page: 1,
          pageSize: 10
        });
      } catch (error) {
        console.error('加载用户失败:', error);
      }
    }
  },
  mounted() {
    this.loadUsers();
  }
};
</script>

5. 高级技巧:使用 TypeScript 增强类型安全

在 TypeScript 项目中,我们可以为 API 添加类型定义,获得更好的开发体验。

typescript 复制代码
// src/types/api.ts
// 用户相关类型
export interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

export interface UserListParams {
  page?: number;
  pageSize?: number;
  keyword?: string;
}

export interface UserListResponse {
  list: User[];
  total: number;
}

// src/api/user.ts
import request from '@/utils/request';
import type { User, UserListParams, UserListResponse } from '@/types/api';

export const userApi = {
  // 获取用户列表(带类型)
  getUsers(params?: UserListParams): Promise<UserListResponse> {
    return request({
      url: '/users',
      method: 'get',
      params
    });
  },
  
  // 获取用户详情(带类型)
  getUserById(id: number): Promise<User> {
    return request({
      url: `/users/${id}`,
      method: 'get'
    });
  },
  
  // 创建用户(带类型)
  createUser(data: Omit<User, 'id'>): Promise<User> {
    return request({
      url: '/users',
      method: 'post',
      data
    });
  }
};

6. 最佳实践总结

  • 统一配置:创建统一的请求实例,配置 baseURL、超时时间、请求头等。
  • 拦截器管理:使用请求/响应拦截器统一处理 token、loading、错误提示等。
  • 模块化组织:按业务模块组织 API,提高代码可维护性。
  • 错误处理:在拦截器中统一处理 HTTP 错误和业务错误。
  • 类型安全:在 TypeScript 项目中为 API 添加类型定义。
  • 环境配置:使用环境变量管理不同环境的 API 地址。
  • 取消请求:对于频繁触发的请求(如搜索),使用 CancelToken 取消之前的请求。

7. 常见问题与解决方案

7.1 如何处理重复请求?

使用 Axios 的 CancelToken 或 AbortController 取消重复请求。

javascript 复制代码
// 使用 CancelToken
let cancelTokenSource = null;

export const searchApi = {
  search(keyword) {
    // 取消之前的请求
    if (cancelTokenSource) {
      cancelTokenSource.cancel('取消重复请求');
    }
    
    // 创建新的 CancelToken
    cancelTokenSource = axios.CancelToken.source();
    
    return request({
      url: '/search',
      method: 'get',
      params: { keyword },
      cancelToken: cancelTokenSource.token
    });
  }
};

7.2 如何添加请求重试机制?

可以在响应拦截器中实现重试逻辑。

javascript 复制代码
// 响应拦截器中添加重试逻辑
request.interceptors.response.use(
  response => {
    return response;
  },
  async error => {
    const config = error.config;
    
    // 设置重试次数
    config.__retryCount = config.__retryCount || 0;
    
    // 检查是否达到最大重试次数
    if (config.__retryCount >= 3) {
      return Promise.reject(error);
    }
    
    // 增加重试计数
    config.__retryCount += 1;
    
    // 创建新的 promise 来处理指数退避
    const backoff = new Promise(resolve => {
      setTimeout(() => {
        resolve();
      }, 1000 * config.__retryCount);
    });
    
    // 等待退避时间后重试请求
    await backoff;
    return request(config);
  }
);
相关推荐
kyriewen2 小时前
我踩了三次同一个坑才明白:React里"改了数据却不重新渲染"的真正原因
前端·javascript·react.js
用户059540174462 小时前
把AI对话记忆测试从手动比对到Pytest+Redis自动化,验证效率提升了10倍,还顺带揪出3个隐蔽bug
前端·css
我星期八休息2 小时前
网络编程—网络层
开发语言·前端·网络·人工智能·智能路由器
java1234_小锋2 小时前
Vue3专题 - 事件处理
javascript·vue.js
A24207349303 小时前
React中请求拦截与响应拦截的统一处理方案
前端·javascript·react.js
zyplayer-doc3 小时前
zyplayer-doc企业知识库能做什么:从文档创建、权限管理到AI问答的完整能力
大数据·javascript·数据库·人工智能·pdf·word
IT_陈寒3 小时前
为什么我的Java Stream流操作会吃掉内存?
前端·人工智能·后端
烬羽4 小时前
《受控 vs 非受控:你以为用对了 useState,直到你写了那个表单》
javascript·react.js·前端框架
嘟嘟07174 小时前
React 受控组件与非受控组件:表单数据到底归谁管?
前端·javascript·react.js