SWR
React Hooks 风格的数据请求缓存库
SWR(Stale-While-Revalidate)是由 Vercel 团队开发的轻量级 React 数据请求库,核心理念是在提供缓存数据的同时异步后台验证更新。gzip 仅约 4-7KB,原生支持 Next.js App Router、RSC 数据预取和 Server Actions 集成。GitHub 32,400+ Stars,是 Next.js 应用的最佳数据获取伴侣。
SWR
React Hooks 风格的数据请求缓存库
技术简介说明
SWR(Stale-While-Revalidate)是由 Vercel 团队开发的轻量级 React 数据请求库,于 2019 年底由前 Vercel 工程师 Shu Umemoto 创建并开源,目前由 Vercel 官方团队持续维护。其名称直接来源于 HTTP 缓存策略 stale-while-revalidate,该策略最早出现在 HTTP/1.1 的 RFC 5861 中,核心理念是:在提供过期(stale)缓存数据的同时,异步在后台发起请求验证并更新数据。
SWR 的设计哲学与 React Hooks 一脉相承——简洁、声明式、可组合。它不试图成为一个全功能的数据管理框架,而是专注于解决一个具体问题:客户端如何高效地获取、缓存和同步服务端数据。这种"做好一件事"的理念,使它在包体大小(gzip 后约 4.2–7KB)和功能实用性之间取得了出色的平衡。
与 Next.js 的关系
SWR 与 Next.js 有着深度的原生集成。作为 Vercel 生态的核心成员,SWR 被设计为 Next.js 应用(无论是 App Router 还是 Pages Router)的最佳数据获取伴侣。它原生支持 React Server Components(RSC)的数据预取、SSR/SSG 的 fallbackData 模式,以及 Next.js 13+ 的流式渲染。在 Server Actions 时代,SWR 仍然在客户端数据缓存和自动重验证方面发挥着不可替代的作用。
Stale-While-Revalidate 理念
传统数据请求有两种模式:
- 缓存优先(Cache-First):只读缓存,用户永远看不到最新数据,除非手动清除
- 网络优先(Network-First):每次等待网络响应,用户需要等待加载
SWR 的 stale-while-revalidate 是两者的优雅折中:
- 第一步:立即渲染缓存中的旧数据(用户体验:零等待)
- 第二步:后台发起网络请求验证数据新鲜度
- 第三步:新数据到达后,无缝更新 UI
这种模式让用户几乎感知不到等待时间,同时保证数据最终一致性,非常适合现代 Web 应用。
基本信息
| 项目 | 内容 |
|---|---|
| 名称 | SWR |
| 官网 | https://swr.vercel.app |
| GitHub | https://github.com/vercel/swr |
| NPM | https://www.npmjs.com/package/swr |
| License | MIT |
| 最新稳定版 | v2.4.2(2026 年 6 月 22 日发布) |
| 维护者 | Vercel / Shu Umemoto(原作者) |
| GitHub Stars | 32,400+ |
| Bundle Size(gzip) | ~4.2–7KB |
| TypeScript | 完整支持,提供精细类型推断 |
| 构建目标 | ES2018(IE11 需额外转译) |
| 核心依赖 | use-sync-external-store(极少) |
快速上手
安装
# npm
npm install swr
# pnpm(推荐)
pnpm add swr
# yarn
yarn add swr基础配置
全局配置(SWRConfig)
通过 SWRConfig 可以为整个应用提供全局默认配置:
import { SWRConfig } from 'swr'
export default function App() {
return (
<SWRConfig
value={{
// 请求失败后重试 3 次
errorRetryCount: 3,
// 窗口获焦时重新验证
revalidateOnFocus: true,
// 网络恢复时重新验证
revalidateOnReconnect: true,
// 数据过期时重新验证
revalidateIfStale: true,
// 全局 fetcher
fetcher: (url: string) => fetch(url).then(res => res.json()),
// 全局 Suspense 配置
suspense: false,
}}
>
<YourApp />
</SWRConfig>
)
}局部配置
单个 useSWR 调用可以覆盖全局配置:
useSWR('/api/user', fetcher, {
revalidateOnFocus: false,
refreshInterval: 5000,
})最小示例
'use client'
import useSWR from 'swr'
// 全局 fetcher
const fetcher = (url: string) => fetch(url).then(res => res.json())
function Profile() {
const { data, error, isLoading } = useSWR('/api/user/123', fetcher)
// 加载中状态
if (isLoading) return <div>加载中...</div>
// 错误状态
if (error) return <div>加载失败:{error.message}</div>
// 成功状态
return <div>用户名:{data.name}</div>
}核心概念与架构
Stale-While-Revalidate 工作机制
SWR 的完整请求流程如下:
┌─────────────────────────────────────────────────────────────────┐
│ 用户访问页面 │
│ ↓ │
│ 检查本地缓存 │
│ ↓ │
│ ┌──有缓存──┐ │
│ │ │ │
│ ↓ ↓ │
│ 立即渲染 后台发起 revalidation 请求 │
│ 旧数据 ↓ │
│ 收到新数据 │
│ ↓ │
│ 更新缓存 + 重新渲染 UI │
│ │
│ ┌──无缓存──┐ │
│ ↓ │ │
│ 直接发起请求,渲染 loading 状态 │
│ ↓ │
│ 收到数据 → 写入缓存 → 渲染 UI │
└─────────────────────────────────────────────────────────────────┘
缓存策略
SWR 的缓存是内存级的,基于 React 组件树生命周期存在:
- 缓存共享:相同 key 的多个组件共享同一份缓存数据
- 缓存持久性:缓存仅在页面生命周期内存在,页面刷新后丢失
- 缓存失效:通过
dedupingInterval(默认 2 秒)控制请求去重 - 按需验证:通过
revalidateIfStale等配置控制何时触发重新验证
Revalidation 触发时机
SWR 支持多种自动重新验证的触发条件:
| 触发条件 | 配置项 | 默认值 |
|---|---|---|
| 窗口获得焦点 | revalidateOnFocus | true |
| 网络重新连接 | revalidateOnReconnect | true |
| 数据过期(stale) | revalidateIfStale | true |
| 组件挂载时 | revalidateOnMount | 视情况而定 |
| 定时轮询 | refreshInterval | 0(关闭) |
| 其他 key 变化触发 | mutate | 手动 |
依赖追踪与请求去重
SWR 通过 key 实现智能的请求去重:
- 相同 key 去重:在
dedupingInterval内,多个组件使用相同 key 只会发起一次请求 - Key 变化感知:key 变化时,旧请求的结果会被自动忽略
- 条件请求:key 为
null时不发起请求(disabled 模式)
// 只有 userId 存在时才发起请求
const { data } = useSWR(userId ? `/api/user/${userId}` : null, fetcher)缓存提供者(Provider)
SWR 支持自定义缓存提供者,可以扩展缓存能力:
import { SWRConfig } from 'swr'
import { useState } from 'react'
function App() {
const [cache] = useState(() => new Map())
return (
<SWRConfig value={{ provider: () => cache }}>
<YourApp />
</SWRConfig>
)
}核心特性
1. 自动重新验证
SWR 在组件挂载后自动发起请求,并在窗口获焦、网络恢复等时机自动重新验证数据,无需手动编写任何刷新逻辑。
// 窗口获焦时自动重新验证
const { data } = useSWR('/api/notifications', fetcher)2. 请求去重
相同 key 的请求在短时间窗口内会被自动去重,避免多个组件同时发起重复请求,减少服务器压力。
// 三个组件使用相同 key,只会发一次请求
function Header() {
const { data: user } = useSWR('/api/user', fetcher)
// ...
}
function Sidebar() {
const { data: user } = useSWR('/api/user', fetcher)
// ...
}3. 本地缓存(mutate)
SWR 提供强大的缓存操作能力,支持乐观更新、回滚和批量变更:
import useSWR, { mutate } from 'swr'
function TodoList() {
const { data, mutate } = useSWR('/api/todos', fetcher)
const addTodo = async (todo) => {
// 乐观更新:先更新 UI,再发请求
mutate(
[...(data || []), todo],
false // 不重新验证
)
// 发送请求
await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(todo)
})
// 重新验证
mutate()
}
}4. 轮询(Polling)
通过 refreshInterval 实现定时轮询,自动在页面隐藏或离线时暂停:
const { data } = useSWR('/api/stats', fetcher, {
refreshInterval: 1000, // 每秒轮询
refreshWhenHidden: false, // 页面隐藏时暂停
refreshWhenOffline: false, // 离线时暂停
})5. Suspense 模式
支持 React Suspense,配合 Suspense boundary 使用:
import { Suspense } from 'react'
import useSWR from 'swr'
function Data() {
const { data } = useSWR('/api/data', fetcher, { suspense: true })
return <div>{data.title}</div>
}
function Page() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Data />
</Suspense>
)
}6. 乐观 UI 更新
通过 useSWRMutation 的 optimisticData 选项实现流畅的乐观更新体验:
import useSWRMutation from 'swr/mutation'
async function addComment(url, { arg }: { arg: Comment }) {
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
})
return res.json()
}
function CommentList() {
const { data, trigger } = useSWRMutation('/api/comments', addComment)
const handleSubmit = (newComment: Comment) => {
trigger(newComment, {
optimisticData: (current) => [...(current || []), newComment],
rollbackOnError: true,
populateCache: true,
revalidate: false,
})
}
}7. 分页与无限滚动
useSWRInfinite 提供开箱即用的分页和无限滚动支持:
import useSWRInfinite from 'swr/infinite'
function UserList() {
const { data, size, setSize, isLoadingMore, isReachingEnd } = useSWRInfinite(
(index, previousPageData) => {
if (previousPageData && !previousPageData.hasMore) return null
return `/api/users?page=${index}`
},
fetcher
)
return (
<div>
{data?.map((users) =>
users.map((user) => <UserCard key={user.id} user={user} />)
)}
<button
onClick={() => setSize(size + 1)}
disabled={isReachingEnd || isLoadingMore}
>
{isLoadingMore ? '加载中...' : isReachingEnd ? '没有更多' : '加载更多'}
</button>
</div>
)
}8. 实时数据订阅
useSWRSubscription 支持 WebSocket、EventSource 等实时数据流:
import useSWRSubscription from 'swr/subscription'
function StockPrice() {
const { data, error } = useSWRSubscription(
'/api/stock/AAPL',
(key, { next }) => {
const ws = new WebSocket(`wss://stocks.example.com/${key}`)
ws.onmessage = (event) => next(null, JSON.parse(event.data))
ws.onerror = (e) => next(e, null)
return () => ws.close()
}
)
if (error) return <div>连接失败</div>
return <div>当前价格:${data?.price}</div>
}9. 预加载(Preload)
v2 新增 preload API,可在组件渲染前发起请求:
import { preload } from 'swr'
// 鼠标悬停时预加载下一页
<Link
href="/dashboard"
onMouseEnter={() => preload('/api/dashboard', fetcher)}
onFocus={() => preload('/api/dashboard', fetcher)}
>
进入仪表盘
</Link>10. 不可变数据(Immutable)
对于不需要重新验证的数据(如历史记录),使用 useSWRImmutable:
import useSWRImmutable from 'swr/immutable'
// 只请求一次,永不重新验证
const { data } = useSWRImmutable('/api/archive/2024', fetcher)生态图
核心包
| 包名 | 用途 | 说明 |
|---|---|---|
swr | 核心库 | 包含 useSWR、SWRConfig、mutate |
swr/mutation | 变更 Hook | useSWRMutation,声明式数据变更 |
swr/infinite | 分页 Hook | useSWRInfinite,无限滚动支持 |
swr/subscription | 订阅 Hook | useSWRSubscription,实时数据流 |
swr/immutable | 不可变 Hook | useSWRImmutable,一次请求永不重验 |
与 Vercel / Next.js 生态关系
┌──────────────────────────────────────────────────────────────┐
│ Vercel 生态 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Next.js │◄──│ SWR │──►│ Vercel Edge Network │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Turbopack│ │ Next.js App Router│ │
│ └──────────┘ └──────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SWR 在 Next.js 中的角色 │ │
│ │ • Server Components 数据预取 + fallback 传递 │ │
│ │ • Client Components 客户端缓存与重验证 │ │
│ │ • Server Actions 变更后的缓存刷新 │ │
│ │ • SSG/SSR 的初始数据注入 │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
开发工具
| 工具 | 类型 | 说明 |
|---|---|---|
swr-devtools | 浏览器扩展 | 社区维护的 SWR DevTools,可查看缓存状态 |
| React DevTools | 官方 | 可查看 SWR 相关的组件状态 |
| Next.js DevTools | 官方 | 包含 SWR 请求的调试支持 |
常用社区扩展
| 包名 | 用途 |
|---|---|
swr-store | 跨组件状态共享 |
swr-global-state | 全局状态管理 |
swr-firestore | Firebase Firestore 集成 |
swr-graphql | GraphQL 请求封装 |
use-swr-storage | IndexedDB 持久化缓存 |
适用场景
1. Next.js / Vercel 应用的数据获取
SWR 是 Next.js 官方推荐的数据获取方案,与 App Router、RSC、Server Actions 深度集成:
// App Router + Server Component 预取 + Client Component 使用 SWR
import { SWRConfig, unstable_serialize } from 'swr'
export default async function Page() {
const data = await fetchUser()
return (
<SWRConfig value={{ fallback: { [unstable_serialize('/api/user')]: data } }}>
<UserProfile />
</SWRConfig>
)
}2. 需要频繁刷新的实时数据
仪表盘、股票行情、消息通知等需要定时轮询或实时更新的场景:
// 仪表盘数据每秒刷新
const { data } = useSWR('/api/dashboard', fetcher, {
refreshInterval: 1000,
revalidateOnFocus: true,
})3. 搜索/过滤场景
利用 keepPreviousData 避免搜索时界面闪烁:
const [query, setQuery] = useState('')
const { data, isLoading } = useSWR(
`/api/search?q=${query}`,
fetcher,
{ keepPreviousData: true, dedupingInterval: 300 }
)4. 表单提交与乐观更新
通过 useSWRMutation 的乐观更新能力,提供流畅的表单交互:
const { trigger, isMutating } = useSWRMutation('/api/comments', addComment)
// 用户点击提交后,评论立即出现在列表中(乐观更新)
await trigger(newComment, {
optimisticData: (data) => [...data, newComment],
rollbackOnError: true,
})5. 无限滚动列表
社交 Feed、商品列表等需要无限滚动的场景:
const { data, size, setSize } = useSWRInfinite(getKey, fetcher)6. 轻量级项目与包体敏感场景
当 bundle size 是重要考量时,SWR(4.2–7KB gzip)相比 TanStack Query(11.4–16.2KB gzip)有明显优势。
7. 需要自动重验证的场景
用户切换标签页、网络恢复后,数据需要自动刷新的场景。SWR 的 revalidation 机制开箱即用,无需额外代码。
开发与工程化
TypeScript 支持
SWR 提供完整的 TypeScript 类型定义:
interface User {
id: number
name: string
email: string
}
// 泛型推断 data 类型为 User | undefined
const { data, error } = useSWR<User>('/api/user/1', fetcher)
// 自定义 fetcher 类型
const fetcher = async (url: string): Promise<User> => {
const res = await fetch(url)
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}错误处理
const { data, error, isLoading } = useSWR('/api/data', fetcher, {
onError: (error, key) => {
console.error(`请求 ${key} 失败:`, error)
},
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
// 404 不重试
if (error.status === 404) return
// 最多重试 3 次
if (retryCount >= 3) return
// 5 秒后重试
setTimeout(() => revalidate({ retryCount }), 5000)
},
})
if (error) {
return <ErrorMessage error={error} />
}测试策略
import { render, screen } from '@testing-library/react'
import useSWR from 'swr'
import { SWRConfig } from 'swr'
// 使用 SWRConfig 提供测试数据
function TestProvider({ children, fallback }: { children: React.ReactNode; fallback: Record<string, any> }) {
return (
<SWRConfig value={{ provider: () => new Map(), fallback }}>
{children}
</SWRConfig>
)
}
test('renders user data from cache', () => {
render(<Profile />, {
wrapper: ({ children }) => (
<TestProvider fallback={{ '/api/user': { name: '测试用户' } }}>
{children}
</TestProvider>
),
})
expect(screen.getByText('测试用户')).toBeInTheDocument()
})Monorepo 配置
在 pnpm + Turborepo monorepo 中使用 SWR:
# 在根目录安装(workspace 共享)
pnpm add swr -w
# 或在具体 app 中安装
pnpm --filter @my-app/web add swr构建与打包
SWR 支持 tree-shaking,只打包实际使用的功能:
// 只导入需要的 hooks
import useSWR from 'swr'
import useSWRMutation from 'swr/mutation'
// useSWRInfinite 和 useSWRSubscription 不导入,不参与打包性能与安全
性能优化
1. 请求去重(Deduplication)
通过 dedupingInterval 避免短时间内重复请求:
const { data } = useSWR('/api/user', fetcher, {
dedupingInterval: 2000, // 2 秒内的相同请求去重
})2. 条件加载(Conditional Fetching)
通过 null key 禁用请求:
const { data } = useSWR(isLoggedIn ? '/api/user' : null, fetcher)3. 预加载(Preload)
提前加载可能需要的数据:
// 在路由切换前预加载
router.prefetch('/dashboard')
preload('/api/dashboard', fetcher)4. 焦点重验证控制
对于频繁切换焦点的场景,可适当关闭:
const { data } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false, // 避免频繁切换标签页时的请求风暴
})安全考虑
CVE-2025-55182(严重 RCE 漏洞)
- 影响版本:v2.3.8 之前的所有版本
- 修复版本:v2.3.8(2025 年 12 月 15 日)
- 类型:远程代码执行(RCE)
- 关联 CVE:CVE-2025-55183、CVE-2025-55184
- 建议:立即升级到 v2.3.8 或更高版本
SSR/CSRF 防护
SWR 本身不处理鉴权,需要在 fetcher 中实现:
const fetcher = async (url: string) => {
const res = await fetch(url, {
credentials: 'include', // 携带 Cookie
headers: {
'X-CSRF-Token': getCsrfToken(), // CSRF 防护
},
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
}敏感数据处理
- 缓存存储在内存中,页面关闭后清除
- 对于敏感数据,避免在 URL key 中包含敏感信息
- 考虑使用
mutate(key, undefined)在离开页面时主动清除缓存
技术对比
SWR vs TanStack Query
| 维度 | SWR v2.4.2 | TanStack Query v5 |
|---|---|---|
| Bundle Size(gzip) | ~4.2–7KB | ~11.4–16.2KB |
| API 风格 | useSWR(key, fetcher, options) 位置参数 | useQuery({ queryKey, queryFn, ... }) 对象配置 |
| 缓存策略 | stale-while-revalidate | stale-while-revalidate |
| DevTools | 社区扩展(只读) | 官方 DevTools(可操作,功能强大) |
| 自动垃圾回收(GC) | ❌ 无 | ✅ 支持(gcTime) |
| 查询取消 | ❌ 不支持 | ✅ 支持(AbortController) |
| staleTime 配置 | ❌ 无原生配置 | ✅ 精细的 staleTime |
| 选择器(Selectors) | ❌ 不支持 | ✅ 支持 |
| 离线 Mutations | ❌ 不支持 | ✅ 支持(Mutation Cache) |
| 乐观更新 | ✅ optimisticData | ✅ 完整生命周期钩子 |
| TypeScript | ✅ 完整支持 | ✅ 更精细的类型推断 |
| 多框架支持 | ❌ 仅 React | ✅ React / Vue / Solid / Svelte |
| Next.js 集成 | ✅ 原生深度集成 | ✅ 良好 |
| 学习曲线 | 低(极简 API) | 中等(功能丰富) |
| Suspense 支持 | ✅ 支持 | ✅ 支持 |
| Server Components | ✅ 官方支持 | ✅ 支持 |
| 乐观 UI | ✅ 内置 | ✅ 内置 |
| 并行请求 | ✅ 自然支持 | ✅ 内置 |
| 依赖追踪 | ✅ 基于 key | ✅ 基于 queryKey |
与其他库对比
| 维度 | SWR | Apollo Client | Axios + 自定义 | RTK Query |
|---|---|---|---|---|
| 定位 | 轻量数据请求 | GraphQL 全栈 | HTTP 客户端 | Redux 数据层 |
| Bundle Size | ~7KB | ~90KB | ~14KB | ~11KB |
| GraphQL 支持 | 需自行封装 | ✅ 原生 | 需自行封装 | 需自行封装 |
| 缓存管理 | ✅ 内存缓存 | ✅ 完整缓存图 | ❌ 无 | ✅ Redux Store |
| 学习曲线 | 低 | 高 | 低 | 中等 |
| 适合场景 | REST/通用 | GraphQL 重度应用 | 简单请求 | Redux 项目 |
| 自动重验证 | ✅ | ✅ | ❌ | ❌ |
选择决策树
你的项目是什么类型?
│
├─ Next.js / Vercel 应用
│ └─ 优先选择 SWR
│
├─ 复杂 SPA(需要 DevTools、离线、精细控制)
│ └─ 优先选择 TanStack Query
│
├─ GraphQL 重度应用
│ └─ 考虑 Apollo Client
│
├─ 已有 Redux 项目
│ └─ 考虑 RTK Query
│
├─ 包体极度敏感
│ └─ 选择 SWR
│
└─ 多框架(Vue / Svelte / Solid)
└─ 选择 TanStack Query
最佳实践
1. 合理设计 Key
// ✅ 好的 key 设计:包含所有影响数据的参数
const { data } = useSWR(`/api/users?page=${page}&sort=${sort}`, fetcher)
// ✅ 数组 key:自动参与依赖追踪
const { data } = useSWR(['/api/users', page, sort], ([url, page, sort]) =>
fetch(`${url}?page=${page}&sort=${sort}`).then(r => r.json())
)
// ❌ 避免:key 中包含随机值或时间戳
const { data } = useSWR(`/api/data?t=${Date.now()}`, fetcher) // 每次都会重新请求2. 使用 isLoading 而非 !data
// ✅ 正确:使用 isLoading 区分首次加载
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (isLoading) return <Spinner /> // 首次加载
if (error) return <Error error={error} />
return <UserData data={data} />
// ❌ 错误:!data 无法区分"无数据"和"加载中"
if (!data) return <Spinner /> // 也可能 data 为 null/undefined 的正常情况3. 乐观更新配合错误回滚
const { data, mutate } = useSWR('/api/todos', fetcher)
const deleteTodo = async (id: string) => {
const originalData = data
// 乐观删除
mutate(data.filter((t) => t.id !== id), false)
try {
await fetch(`/api/todos/${id}`, { method: 'DELETE' })
mutate() // 重新验证
} catch (error) {
// 回滚
mutate(originalData, false)
}
}4. 全局 mutate 批量清除
import { mutate } from 'swr'
// 清除所有用户相关缓存
mutate(
(key) => typeof key === 'string' && key.startsWith('/api/users'),
undefined,
{ revalidate: false }
)5. 结合 Next.js Server Actions
'use client'
import useSWR from 'swr'
import { mutate } from 'swr'
export function CommentList() {
const { data: comments } = useSWR('/api/comments', fetcher)
return (
<ul>
{comments?.map((c) => <li key={c.id}>{c.text}</li>)}
</ul>
)
}
export function CommentForm() {
const addComment = async (formData: FormData) => {
'use server'
const text = formData.get('text')
await db.comments.create({ text })
// Server Action 执行后,重新验证评论列表
mutate('/api/comments')
}
return (
<form action={addComment}>
<input name="text" />
<button type="submit">提交</button>
</form>
)
}6. 预加载与路由 Prefetch
'use client'
import { preload } from 'swr'
import Link from 'next/link'
function NavItem({ href, children }: { href: string; children: React.ReactNode }) {
return (
<Link
href={href}
onMouseEnter={() => preload(`/api/page/${href}`, fetcher)}
onFocus={() => preload(`/api/page/${href}`, fetcher)}
>
{children}
</Link>
)
}7. 错误边界配合
import { ErrorBoundary } from 'react-error-boundary'
import { Suspense } from 'react'
import useSWR from 'swr'
function DataComponent() {
const { data } = useSWR('/api/data', fetcher, {
suspense: true, // 抛出 Promise 给 Suspense
})
return <div>{data.title}</div>
}
function Page() {
return (
<ErrorBoundary fallback={<div>出错了</div>}>
<Suspense fallback={<div>加载中...</div>}>
<DataComponent />
</Suspense>
</ErrorBoundary>
)
}8. 缓存提供者优化
import { SWRConfig } from 'swr'
import { useState } from 'react'
export function App() {
// 使用 Map 作为缓存提供者,提升性能
const [cache] = useState(() => new Map())
return (
<SWRConfig
value={{
provider: () => cache,
// 清理策略(配合页面可见性)
isVisible: () => document.visibilityState === 'visible',
}}
>
<YourApp />
</SWRConfig>
)
}技术局限与边界
1. 无内置持久化缓存
SWR 的缓存是内存级的,页面刷新后丢失。如需持久化,需要自行实现:
// 需要自行集成 localStorage / IndexedDB
import localforage from 'localforage'
const persistentFetcher = async (key: string) => {
// 先尝试从 IndexedDB 读取
const cached = await localforage.getItem(key)
if (cached) return cached
// 否则从网络获取
const data = await fetch(key).then(r => r.json())
await localforage.setItem(key, data)
return data
}2. 无自动垃圾回收
与 TanStack Query 不同,SWR 不会自动清理过期缓存。长时间运行的应用需要注意内存占用。
3. 无查询取消
SWR 不支持 AbortController,无法主动取消正在进行的请求。对于需要取消能力的场景,需要使用其他方案。
4. 无 DevTools 官方支持
SWR 没有官方 DevTools,社区工具功能有限(只读,无法操作缓存)。
5. 仅支持 React
SWR 仅支持 React 生态,不支持 Vue、Svelte、Solid 等其他框架。
6. staleTime 配置缺失
无法像 TanStack Query 那样配置数据"新鲜期"(staleTime),只能通过 dedupingInterval 控制请求去重。
7. Server Components 限制
SWRConfig 和 unstable_serialize 可在 Server Component 中使用,但 Hook API(useSWR 等)需要 'use client' 指令,无法在 Server Component 中直接使用。
8. 复杂场景能力不足
对于以下复杂场景,SWR 的能力有限:
- 复杂的查询依赖链(Query A 完成后才能触发 Query B)
- 精细的缓存控制(指定 key 的 staleTime、gcTime)
- 离线优先(Offline-First)应用
- 多标签页状态同步
学习资源
官方资源
| 资源 | 链接 |
|---|---|
| 官方文档 | https://swr.vercel.app |
| GitHub 仓库 | https://github.com/vercel/swr |
| v2.0 发布公告 | https://swr.vercel.app/blog/swr-v2 |
| Examples | https://github.com/vercel/swr/tree/main/examples |
| Next.js 集成指南 | https://swr.vercel.app/docs/with-nextjs |
中文资源
| 资源 | 说明 |
|---|---|
| SWR 中文文档 | 社区维护的中文翻译文档 |
| 掘金 - SWR 入门指南 | 多篇 SWR 实战文章 |
| 掘金 - SWR vs React Query 全面比较 | 2025 年最新对比 |
教程与文章
| 资源 | 类型 |
|---|---|
| SWR Official Course - Vercel | 官方教程(含 SWR) |
| LogRocket - SWR vs TanStack Query | 对比文章 |
| Refine - React Query vs SWR 2025 | 2025 对比指南 |
| SISL - React Query vs SWR 2026 | 2026 对比 |
视频资源
| 资源 | 平台 |
|---|---|
| Vercel 官方 YouTube 频道 | YouTube |
| Next.js Conf 演讲 | YouTube |
| 中文技术博主的 SWR 教程 | Bilibili |
2026 年现状
版本状态
截至 2026 年 7 月,SWR 的最新稳定版本为 v2.4.2(2026 年 6 月 22 日发布)。该版本修复了双重取消订阅 bug,防止已解析的 Promise 导致组件挂起,并将 CI 升级到 Node 24。
近期版本演进
| 版本 | 发布时间 | 关键变更 |
|---|---|---|
| v2.4.2 | 2026.06.22 | 修复双重取消订阅 bug;防止已解析 Promise 挂起 |
| v2.4.1 | 2026.06.22 | 移除废弃的 downlevelIteration 选项 |
| v2.4.0 | 2026.02.01 | 修复 useSWRImmutable 覆盖全局 refreshInterval;减少多余渲染 |
| v2.3.8 | 2025.12.15 | 🔒 安全修复:CVE-2025-55182(严重 RCE 漏洞) |
| v2.3.7 | 2025.11.28 | 新增 strictServerPrefetchWarning |
| v2.3.6 | 2025.08.11 | 修复 Suspense 未启用时报错 |
| v2.3.5 | 2025.08.09 | 性能优化:useSWRConfig 使用 useMemo |
维护活跃度
- ✅ 持续维护:最近 1 个月内有多次发布
- ✅ 安全响应及时:v2.3.8 及时修复了严重 RCE 漏洞
- ✅ CI 现代化:已升级到 Node 24、pnpm 最新、Playwright 测试
- ✅ React 18/19 兼容:支持
useSyncExternalStore、Suspense、并发渲染 - ✅ Next.js 深度集成:App Router、RSC、Server Components 官方支持
Server Actions 时代的影响
随着 Next.js 14/15 引入 Server Actions,社区曾讨论 SWR 的定位是否会受到影响。结论是:
- Server Actions 处理变更(表单提交、数据修改)
- SWR 处理读取和缓存(客户端数据获取、缓存、自动重验证)
- 两者互补而非替代:Server Actions 变更后,通过
mutate()刷新 SWR 缓存
// Server Action 处理后刷新 SWR 缓存
import { mutate } from 'swr'
async function updateProfile(formData: FormData) {
'use server'
await db.users.update(/* ... */)
mutate('/api/profile') // 通知 SWR 重新验证
}生态前景
SWR 在 2026 年仍然是 React 生态中最受欢迎的数据请求库之一,尤其适合:
- Next.js / Vercel 应用(官方推荐)
- 包体敏感的项目
- 中小复杂度的数据获取场景
- 需要快速上手的项目
对于复杂的数据管理场景(DevTools、离线支持、查询取消),TanStack Query 仍然是更全面的选择。但在其核心定位(轻量、快速、React 风格)上,SWR 没有直接竞争对手。
总结
SWR 是一个理念清晰、实现简洁、性能优秀的 React 数据请求库。它将 HTTP 缓存策略 stale-while-revalidate 优雅地映射到 React Hooks API,让开发者可以用极少的代码实现高效的数据获取和缓存管理。
选择 SWR 当你:
- 构建 Next.js / Vercel 应用
- 重视包体大小
- 需要快速上手,API 简洁
- 项目复杂度中等,不需要精细的缓存控制
考虑其他方案当你:
- 需要官方 DevTools
- 需要查询取消、离线支持
- 需要跨框架(Vue / Svelte / Solid)
- 项目复杂度极高,需要精细的缓存策略
SWR 证明了"做好一件事"的价值——在数据请求这个细分领域,它提供了最简洁、最高效的解决方案。