人人都会AI编程

14.3 SWR:缓存优先的请求方案

更新时间:2026-07-10

SWR(stale-while-revalidate)是由 Vercel 开源的一个 React Hooks 数据请求库,它的核心策略正如其名:先返回缓存(stale)数据,同时在后台发起请求(revalidate),最后用最新数据更新 UI。这个策略源自 HTTP 缓存头 Cache-Control: stale-while-revalidate 的启发,被 SWR 完美地搬到了前端数据请求层。

为什么需要 SWR?

传统的数据请求思路是:组件挂载时发起请求,在 loading 状态时展示加载动画,请求成功后渲染数据。每当页面切换或组件重新挂载,又要经历一次“加载 → 渲染”的循环。用户会频繁看到加载提示,体验割裂。

SWR 的解决之道是:只要请求过,数据就会被缓存。下次需要同样数据时,SWR 会立刻把缓存数据给你,让你先渲染界面,然后悄悄去后台发一个请求,拿到新数据后再静默更新界面。用户几乎感受不到加载过程,只有数据变动时才会被平滑更新。

快速上手

npm install swr

一个最基础的 SWR 使用:

import useSWR from 'swr';

// fetcher 负责实际的数据请求,可以是 fetch、axios 等任何异步函数
const fetcher = (url) => 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>请求失败</div>;

  return <div>你好,{data.name}</div>;
}

第一次请求时 isLoadingtrue,展示加载中;一旦数据被缓存,后续访问时 data 会立即从缓存中取得,isLoading 变为 false,此时 SWR 同时在后台重新验证(revalidate),若远端数据有变化则自动更新组件。

核心缓存与自动重新验证

SWR 的缓存默认基于 key(通常是请求 URL)。相同的 key 共享同一份数据和状态。每当出现以下情况时,SWR 会自动触发重新验证,保持数据新鲜:

  • 组件挂载时(首次)
  • 用户重新聚焦浏览器标签页(默认开启)
  • 网络恢复时(默认开启)
  • 以固定时间间隔轮询(需手动配置 refreshInterval

这些行为都可以通过配置关闭或调整:

const { data } = useSWR('/api/data', fetcher, {
  revalidateOnFocus: false,       // 关闭聚焦时重新验证
  revalidateOnReconnect: false,   // 关闭网络恢复时重新验证
  refreshInterval: 5000,          // 每 5 秒轮询一次
});

缓存优先的关键体验

设想一个典型场景:用户在页面 A 加载了用户列表,然后导航到页面 B 查看详情,再返回页面 A。在没有 SWR 的传统写法中,返回页面 A 时通常需要重新请求用户列表,用户会看到短暂的“加载中”。而 SWR 让页面 A 立即用之前的缓存渲染,同时在后台静默请求新数据,如果列表有变化,UI 会无缝更新。这个体验的提升不需要开发者写复杂的缓存逻辑,SWR 全部内置。

处理错误与重试

SWR 会在请求失败时自动进入错误状态,并且提供重试机制。你可以通过 onErrorRetry 定制重试策略:

const { data, error } = useSWR('/api/data', fetcher, {
  onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
    // 永不重试 404 错误
    if (error.status === 404) return;
    // 最多重试 3 次
    if (retryCount >= 3) return;
    // 递增延迟重试
    setTimeout(() => revalidate({ retryCount }), 5000);
  }
});

条件请求与依赖请求

SWR 支持条件请求,例如只有 userId 存在时才发起请求:

const { data } = useSWR(userId ? `/api/user/${userId}` : null, fetcher);

如果 keynullfalse,SWR 不会发起请求,非常适合处理依赖关系。

手动修改缓存(乐观更新)

SWR 提供了 mutate 方法来手动更新缓存,常用于乐观更新——先让 UI 立刻显示新状态,再向服务器发送请求:

import useSWR, { useSWRConfig } from 'swr';

function TodoList() {
  const { data, mutate } = useSWR('/api/todos', fetcher);
  const { mutate: globalMutate } = useSWRConfig(); // 全局 mutate

  const addTodo = async (newTodo) => {
    // 1. 乐观更新:立即将新数据加入缓存
    mutate([...data, newTodo], false);

    // 2. 发送请求
    await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(newTodo),
    });

    // 3. 重新验证,保证缓存与服务器一致
    mutate();
  };
  // ...
}

调用 mutate(key, data, shouldRevalidate) 可以更新对应 key 的缓存,第三个参数设为 false 表示不立刻发起重新验证(因为我们已经知道预期结果)。请求完成后再次调用 mutate() 触发一次验证,确保最终一致性。

SWR 与 React Query 的差异

SWR 和 TanStack Query(React Query)都是优秀的数据请求与缓存库,但设计哲学上略有区别:

| 特性 | SWR | React Query |
|------|-----|-------------|
| 体积 | 更小(约 5KB gzipd) | 相对较大(约 12KB gzipd) |
| API 复杂度 | 极简,核心只有 useSWR | 提供更多 hook(useQuery, useMutation 等) |
| 缓存机制 | 基于 key 的内存缓存 | 更可配置的缓存、垃圾回收、持久化 |
| 开发工具 | DevTools 支持 | 更强大的 DevTools,查询检查器 |
| 扩展能力 | 通过中间件/插件 | 插件体系 + 内置更多配置项 |

SWR 更适合偏好轻量、极简 API 的团队或小型项目;React Query 则提供了更完整的工具链,在复杂数据管理场景下发挥更全面。两者都能出色地完成数据请求与缓存任务,没有绝对的好坏,只有匹配场景的合适与否。

实际应用中的注意事项

  1. fetcher 必须平稳:SWR 不关心 fetcher 内部细节,你可以用 fetch、axios 或任何 Promise 函数,但需保证错误能被正确抛出,以便 SWR 捕获并进入 error 状态。
  1. 避免在 useEffect 中重复请求:SWR 已经处理了自动重新验证,不要再自己加 useEffect 发起相同请求,否则会破坏缓存优势。
  1. 缓存 key 的不稳定性:如果 fetcher 中使用了依赖项(如 token、分页参数),应该使用数组作为 key,保证唯一性:
   const { data } = useSWR(['/api/data', page, filter], ([url, page, filter]) => 
     fetcher(`${url}?page=${page}&filter=${filter}`)
   );
   
  1. 预请求数据:SWR 支持在组件挂载前“预热”缓存,提升首屏速度:
   import { preload } from 'swr';
   // 在可能进入页面前调用
   preload('/api/user', fetcher);
   

总结

SWR 用简单到几乎透明的 API 实现了缓存优先的请求策略,极大地优化了用户体验。它把“先展示后更新”这一理念变成开箱即用的默认行为,同时提供了手动缓存控制、自动重新验证、错误重试等实用功能。对于多数需要频繁请求数据的 React 应用,SWR 能帮你去掉大量冗余的加载状态和缓存逻辑,让代码更专注、体验更流畅。