🚀 SWR + Axios API 层完整指南
本文介绍如何在 React / Next.js 项目中,结合 SWR 与 Axios 构建高效、可维护的前端 API 请求层。
特点:
- ✅ 请求缓存、去重与自动重验证(SWR)
- ✅ 拦截器统一处理(Axios)
- ✅ 统一错误、类型与返回格式
- ✅ 易于扩展与调试
📦 一、安装依赖
# 推荐使用 pnpm
pnpm add swr axios
# 或 npm
npm install swr axios
# 或 yarn
yarn add swr axios
🧩 二、项目目录结构推荐
src/
├── api/
│ ├── axiosInstance.ts # axios 实例封装
│ ├── fetcher.ts # swr fetcher 封装
│ ├── hooks/
│ │ ├── useUser.ts # 示例:获取用户信息
│ │ └── usePosts.ts # 示例:获取文章列表
│ ├── types/
│ │ └── api.d.ts # 定义接口类型
│ └── index.ts # API 导出入口
├── pages/
│ ├── index.tsx
│ └── user.tsx
└── ...
⚙️ 三、Axios 实例封装
创建一个通用
axiosInstance.ts,统一配置 baseURL、超时、token 拦截与错误处理。
// src/api/axiosInstance.ts
import axios from "axios";
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || "https://api.example.com",
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
console.warn("未授权或登录过期");
// 可以跳转登录页
}
return Promise.reject(error);
}
);
export default axiosInstance;
🔄 四、SWR 的 Fetcher 封装
useSWR的第二个参数是 “数据获取函数 (fetcher)”,这里使用上面的axiosInstance。
// src/api/fetcher.ts
import axiosInstance from "./axiosInstance";
export const fetcher = (url: string) => axiosInstance.get(url);
也可以支持 POST / PATCH / DELETE:
// src/api/fetcher.ts (增强版)
export const postFetcher = <T = any>(url: string, data?: any) =>
axiosInstance.post<T>(url, data);
export const putFetcher = <T = any>(url: string, data?: any) =>
axiosInstance.put<T>(url, data);
export const deleteFetcher = <T = any>(url: string) =>
axiosInstance.delete<T>(url);
🪝 五、定义自定义 Hooks
用于在组件中更简洁地使用 SWR。
1️⃣ 获取用户信息
// src/api/hooks/useUser.ts
import useSWR from "swr";
import { fetcher } from "../fetcher";
import type { User } from "../types/api";
export function useUser(userId?: string) {
const { data, error, isLoading, mutate } = useSWR<User>(
userId ? `/users/${userId}` : null, // null 表示不请求
fetcher
);
return {
user: data,
isLoading,
isError: !!error,
mutate, // 可手动触发重新请求
};
}
2️⃣ 获取文章列表
// src/api/hooks/usePosts.ts
import useSWR from "swr";
import { fetcher } from "../fetcher";
import type { Post } from "../types/api";
export function usePosts() {
const { data, error, isLoading, mutate } = useSWR<Post[]>("/posts", fetcher);
return {
posts: data || [],
isLoading,
isError: !!error,
mutate,
};
}
📘 六、类型定义
// src/api/types/api.d.ts
export interface User {
id: string;
name: string;
email: string;
}
export interface Post {
id: number;
title: string;
content: string;
authorId: string;
}
🧠 七、在组件中使用
1️⃣ 获取用户信息
// src/pages/user.tsx
"use client";
import { useUser } from "@/api/hooks/useUser";
export default function UserPage() {
const { user, isLoading, isError } = useUser("123");
if (isLoading) return <p>加载中...</p>;
if (isError) return <p>加载失败</p>;
return (
<div>
<h2>用户信息</h2>
<p>姓名:{user?.name}</p>
<p>邮箱:{user?.email}</p>
</div>
);
}
2️⃣ 获取文章列表
// src/pages/index.tsx
"use client";
import { usePosts } from "@/api/hooks/usePosts";
export default function HomePage() {
const { posts, isLoading } = usePosts();
if (isLoading) return <p>加载中...</p>;
return (
<div>
<h2>文章列表</h2>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
🔁 八、SWR 进阶配置
你可以在全局提供默认配置:
// src/app/layout.tsx 或 _app.tsx
import { SWRConfig } from "swr";
import { fetcher } from "@/api/fetcher";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
dedupingInterval: 2000, // 2秒内重复请求自动去重
revalidateOnFocus: true, // 页面聚焦时重新请求
shouldRetryOnError: false, // 请求失败不自动重试
}}
>
{children}
</SWRConfig>
);
}
🧩 九、手动更新缓存(mutate)
import useSWR, { mutate } from "swr";
const { data } = useSWR("/posts", fetcher);
// 局部更新
mutate("/posts");
// 乐观更新(无需重新请求)
mutate("/posts", [...data, { id: 999, title: "新文章" }], false);
🧰 十、可选增强方案
| 功能 | 推荐方案 |
|---|---|
| 数据类型安全 | 使用 zod 或 TypeScript 接口 |
| 请求失败提示 | 结合 react-toastify / antd message |
| 全局错误边界 | 自定义 SWR errorHandler |
| SSR 支持(Next.js) | 使用 useSWRImmutable 或 fallbackData |
| POST/PUT 请求 Hook 化 | 使用 useSWRMutation(SWR 2.x 新增) |
🧾 十一、SWR 常用参数说明
| 参数 | 作用 |
|---|---|
fetcher | 请求函数(返回 Promise) |
dedupingInterval | 请求去重时间 |
revalidateOnFocus | 页面重新聚焦时自动刷新数据 |
fallbackData | 初始数据 |
refreshInterval | 自动轮询刷新间隔 |
mutate | 手动刷新缓存数据 |
🧠 十二、总结
| 优势 | 说明 |
|---|---|
| ⚡️ 性能优化 | 请求缓存 + 自动去重 |
| 🔁 实时同步 | 聚焦或重新连接时自动更新 |
| 🧩 模块化 | Hook 形式组织 API 请求逻辑 |
| 💎 易维护 | Axios 拦截统一处理 Token 与错误 |
✅ 示例仓库结构(可直接复制)
src/
├── api/
│ ├── axiosInstance.ts
│ ├── fetcher.ts
│ ├── hooks/
│ │ ├── useUser.ts
│ │ └── usePosts.ts
│ ├── types/
│ │ └── api.d.ts
│ └── index.ts
├── app/
│ ├── layout.tsx
│ └── page.tsx
└── ...