SWR logo

SWR

React Hooks 风格的数据请求缓存库

精选工具HTTP请求API数据缓存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 理念

传统数据请求有两种模式:

  1. 缓存优先(Cache-First):只读缓存,用户永远看不到最新数据,除非手动清除
  2. 网络优先(Network-First):每次等待网络响应,用户需要等待加载

SWR 的 stale-while-revalidate 是两者的优雅折中:

  • 第一步:立即渲染缓存中的旧数据(用户体验:零等待)
  • 第二步:后台发起网络请求验证数据新鲜度
  • 第三步:新数据到达后,无缝更新 UI

这种模式让用户几乎感知不到等待时间,同时保证数据最终一致性,非常适合现代 Web 应用。


基本信息

项目内容
名称SWR
官网https://swr.vercel.app
GitHubhttps://github.com/vercel/swr
NPMhttps://www.npmjs.com/package/swr
LicenseMIT
最新稳定版v2.4.2(2026 年 6 月 22 日发布)
维护者Vercel / Shu Umemoto(原作者)
GitHub Stars32,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 支持多种自动重新验证的触发条件:

触发条件配置项默认值
窗口获得焦点revalidateOnFocustrue
网络重新连接revalidateOnReconnecttrue
数据过期(stale)revalidateIfStaletrue
组件挂载时revalidateOnMount视情况而定
定时轮询refreshInterval0(关闭)
其他 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 更新

通过 useSWRMutationoptimisticData 选项实现流畅的乐观更新体验:

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核心库包含 useSWRSWRConfigmutate
swr/mutation变更 HookuseSWRMutation,声明式数据变更
swr/infinite分页 HookuseSWRInfinite,无限滚动支持
swr/subscription订阅 HookuseSWRSubscription,实时数据流
swr/immutable不可变 HookuseSWRImmutable,一次请求永不重验

与 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-firestoreFirebase Firestore 集成
swr-graphqlGraphQL 请求封装
use-swr-storageIndexedDB 持久化缓存

适用场景

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.2TanStack Query v5
Bundle Size(gzip)~4.2–7KB~11.4–16.2KB
API 风格useSWR(key, fetcher, options) 位置参数useQuery(&#123; queryKey, queryFn, ... &#125;) 对象配置
缓存策略stale-while-revalidatestale-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

与其他库对比

维度SWRApollo ClientAxios + 自定义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 限制

SWRConfigunstable_serialize 可在 Server Component 中使用,但 Hook API(useSWR 等)需要 'use client' 指令,无法在 Server Component 中直接使用。

8. 复杂场景能力不足

对于以下复杂场景,SWR 的能力有限:

  • 复杂的查询依赖链(Query A 完成后才能触发 Query B)
  • 精细的缓存控制(指定 key 的 staleTime、gcTime)
  • 离线优先(Offline-First)应用
  • 多标签页状态同步

学习资源

官方资源

中文资源

资源说明
SWR 中文文档社区维护的中文翻译文档
掘金 - SWR 入门指南多篇 SWR 实战文章
掘金 - SWR vs React Query 全面比较2025 年最新对比

教程与文章

视频资源

资源平台
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.22026.06.22修复双重取消订阅 bug;防止已解析 Promise 挂起
v2.4.12026.06.22移除废弃的 downlevelIteration 选项
v2.4.02026.02.01修复 useSWRImmutable 覆盖全局 refreshInterval;减少多余渲染
v2.3.82025.12.15🔒 安全修复:CVE-2025-55182(严重 RCE 漏洞)
v2.3.72025.11.28新增 strictServerPrefetchWarning
v2.3.62025.08.11修复 Suspense 未启用时报错
v2.3.52025.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 的定位是否会受到影响。结论是:

  1. Server Actions 处理变更(表单提交、数据修改)
  2. SWR 处理读取和缓存(客户端数据获取、缓存、自动重验证)
  3. 两者互补而非替代: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 证明了"做好一件事"的价值——在数据请求这个细分领域,它提供了最简洁、最高效的解决方案。