手把手教你写出优雅的 API 接口调用

各位前端打工人,是不是经常在后端接口文档的汪洋大海中迷失?或者看着自己几个月前写的 fetchaxios 代码,忍不住想问一句:"这到底是谁写的?哦,原来是我自己。"

今天,咱们就来聊聊如何优雅地封装和使用 API。不扯高深莫测的架构,只讲让你看了就能直接复制粘贴、老板看了直呼内行的实战技巧。文章会掰开揉碎了讲,保证你不仅知其然,还知其所以然!

第一步:给 Axios 找个"大管家"

很多新手喜欢在每个组件里直接 import axios,然后满世界写 axios.get('/api/users')。这就好比你每次出门都要重新买一辆自行车,骑完就扔,不仅费钱还费事。

我们需要创建一个全局的 Axios 实例,也就是我们的"大管家"。

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

const instance = axios.create({
  baseURL: '/api', // 统一前缀,再也不用到处拼字符串了
  timeout: 10000,
});

export default instance;

代码拆解与实现原理:

  • axios.create():这是 Axios 提供的一个工厂方法。它的作用是创建一个全新的 Axios 实例。为什么要新建而不是直接用全局的 axios?因为全局配置是单例的,一旦修改会影响整个项目。而独立实例只作用于我们导出的这个对象,方便我们做定制化的配置。
  • baseURL: '/api':这个配置非常关键!它会自动拼接在你后续请求的 URL 前面。比如你写 instance.get('/users'),实际发出的请求就是 /api/users。配合 Vite 或 Webpack 的 Proxy 代理,可以完美解决跨域问题。
  • timeout: 10000:设置请求超时时间为 10 秒。如果后端接口卡死,10 秒后 Axios 会自动抛出超时错误,避免用户的页面一直转圈圈。

第二步:拦截器,你的全自动"保镖"

为什么每次请求都要手动去 localStorage 里抠 Token?为什么每次 401 都要在各个页面写一遍跳转登录页的逻辑?拦截器就是用来解决这些重复劳动的。

1. 请求拦截器:负责在出门前帮你把通行证(Token)挂好。

ini 复制代码
instance.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

代码拆解与实现原理:

  • interceptors.request.use():这是 Axios 的请求拦截器。在每一个请求真正发出去之前,都会先经过这里。
  • config 参数:它包含了这次请求的所有配置信息(URL、请求头、请求体等)。
  • config.headers.Authorization:我们在请求头中注入 Token。后端通常会校验这个 Header,确认你是不是合法用户。Bearer 是 JWT 的标准前缀,后面跟一个空格和 Token 字符串。
  • return config注意,这里必须返回 config! 如果你忘了 return,或者返回了 undefined,这个请求就会直接中断,前端会报错。

2. 响应拦截器:负责在回来后帮你把包装拆了,顺便处理全局异常。

ini 复制代码
instance.interceptors.response.use(
  response => response.data, // 剥掉 axios 的外壳,只留核心数据
  error => {
    if (error.response?.status === 401) {
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

代码拆解与实现原理:

  • response => response.data:Axios 默认返回的数据结构是 { data: {...}, status: 200, headers: {...} }。但在业务代码里,我们 99% 的情况只需要 data 里面的内容。在这里统一剥壳,以后在组件里拿到数据就可以直接用,不用再写 .data.data 这种丑陋的代码。
  • error.response?.status === 401:401 代表"未授权"(Token 过期或无效)。使用可选链 ?. 是为了防止网络断开时 error.response 为 undefined 导致报错。
  • Promise.reject(error):对于非 401 的其他错误,我们不仅要处理,还要把错误继续抛出去,让调用方的 .catch() 能够捕获到。

第三步:API 模块化,告别"面条代码"

有了封装好的 instance,千万别直接在页面里写 instance.post('/posts')。我们要按业务模块把 API 分门别类。

文章模块 (api/post.ts)

typescript 复制代码
import axios from '@/utils/request';

export const fetchPosts = (page: number = 1, limit: number = 10) => {
  return axios.get('/posts', { params: { page, limit } });
};

代码拆解与实现原理:

  • params: { page, limit }:这是 GET 请求传参的标准姿势。Axios 会自动将对象序列化为 URL 查询字符串,最终发出的请求就是 /posts?page=1&limit=10。如果你手动拼接字符串,不仅容易出错,还容易引发 XSS 风险。

AI 模块 (api/ai.ts)

typescript 复制代码
import axios from '@/utils/request';

export const askAi = (question: string) => {
  return axios.post('/ai/rag', { question });
};

代码拆解与实现原理:

  • 第二个参数 { question }:这是 POST 请求的 Request Body(请求体)。Axios 会自动将其转换为 JSON 格式,并在请求头中加上 Content-Type: application/json。后端只要按照 JSON 解析就能拿到数据。

第四步:在组件里"躺平"

当你把上面的工作做完后,你会发现,在 React 或 Vue 组件里写接口调用,简直是一种享受。

javascript 复制代码
import { useEffect, useState } from 'react';
import { fetchPosts } from '@/api/post';

const PostList = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // 直接调用,干净利落
    fetchPosts(1, 20).then(res => {
      // 因为响应拦截器已经剥壳,这里的 res 就是后端返回的真实数据
      setPosts(res.data); 
    }).catch(err => {
      console.log('获取文章失败', err);
    });
  }, []);

  return <div>{/* 渲染你的文章列表 */}</div>;
};

代码拆解与实现原理:

  • 业务代码与底层逻辑彻底解耦。如果有一天后端把 /posts 改成了 /articles,你只需要去 api/post.ts 里改一行代码,整个项目成百上千个组件完全不需要动!这就是工程化的威力。

进阶彩蛋:无感刷新 Token

如果你的项目对安全性要求很高,Token 过期时间很短,你可以利用"请求队列"机制。当遇到 401 时,先挂起后续所有请求,偷偷用 Refresh Token 换一个新 Token,然后再把挂起的请求全部放行。

这就好比你在过安检时身份证过期了,安检员(拦截器)让你稍等,他跑去后台给你办了个临时通行证,然后把你后面排队的人依次放行,全程丝滑,用户根本感觉不到 Token 刷新过。

总结

优雅的 API 调用其实就三步:

  1. 建实例:统一 baseURL 和超时时间。
  2. 加拦截:自动注入 Token,自动解包响应,统一处理报错。
  3. 分模块:按业务划分 API 文件,保持组件代码的清爽。

记住,代码是写给人看的,顺便给机器运行。把繁琐的底层逻辑封装起来,把优雅和简洁留给业务组件,这就是前端工程化的魅力!如果觉得有用,别忘了点赞收藏,下次写项目直接拿出来抄!

相关推荐
#麻辣小龙虾#1 小时前
vue3基于leaflet.js实现地图编辑功能
javascript·ecmascript·leaflet.js
奇奇怪怪的1 小时前
浏览器线程与进程深度剖析
前端
YHL1 小时前
🧊 CSS 3D 硬核解析:四个属性手写旋转立方体
前端·css·html
spmcor1 小时前
JavaScript 日期限制的“三个月陷阱”:从边界溢出到稳健实现
javascript
半个落月1 小时前
Ajax 异步编程全攻略:从 XHR 到 async/await
javascript
毛骗导演1 小时前
Tool Boundary:如何让大模型永远不知道也不会泄露用户敏感数据
前端·架构
零瓶水Herwt2 小时前
代替vue-currency-input使用原生货币符号
前端·vue.js
Moment2 小时前
从多人编辑到 Agent 写文档,Hocuspocus v4 正在改写协同系统 😍😍😍
前端·后端·面试