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>;
}
第一次请求时 isLoading 为 true,展示加载中;一旦数据被缓存,后续访问时 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);
如果 key 为 null 或 false,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 则提供了更完整的工具链,在复杂数据管理场景下发挥更全面。两者都能出色地完成数据请求与缓存任务,没有绝对的好坏,只有匹配场景的合适与否。
实际应用中的注意事项
- fetcher 必须平稳:SWR 不关心 fetcher 内部细节,你可以用 fetch、axios 或任何 Promise 函数,但需保证错误能被正确抛出,以便 SWR 捕获并进入
error状态。
- 避免在 useEffect 中重复请求:SWR 已经处理了自动重新验证,不要再自己加
useEffect发起相同请求,否则会破坏缓存优势。
- 缓存 key 的不稳定性:如果 fetcher 中使用了依赖项(如 token、分页参数),应该使用数组作为 key,保证唯一性:
const { data } = useSWR(['/api/data', page, filter], ([url, page, filter]) =>
fetcher(`${url}?page=${page}&filter=${filter}`)
);
- 预请求数据:SWR 支持在组件挂载前“预热”缓存,提升首屏速度:
import { preload } from 'swr';
// 在可能进入页面前调用
preload('/api/user', fetcher);
总结
SWR 用简单到几乎透明的 API 实现了缓存优先的请求策略,极大地优化了用户体验。它把“先展示后更新”这一理念变成开箱即用的默认行为,同时提供了手动缓存控制、自动重新验证、错误重试等实用功能。对于多数需要频繁请求数据的 React 应用,SWR 能帮你去掉大量冗余的加载状态和缓存逻辑,让代码更专注、体验更流畅。