🚀 SWR + Axios API 层完整指南

139 阅读2分钟

🚀 SWR + Axios API 层完整指南

本文介绍如何在 React / Next.js 项目中,结合 SWRAxios 构建高效、可维护的前端 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)使用 useSWRImmutablefallbackData
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
 └── ...