SWR 完全指南:掌握缓存策略和乐观更新的实战技巧

周五下午三点,我盯着屏幕上那个熟悉的loading转圈,第N次刷新用户列表页面。每次切换tab,loading;每次从别的页面回来,loading;哪怕只是因为接了个电话导致浏览器失焦,回来又是loading。最抓狂的是什么?那些数据明明一秒钟前才加载过。
我猜你也遇到过这种情况——写React项目时,每个数据获取都得写一遍useState、useEffect,再加上loading、error的处理逻辑。20行代码打底。更头疼的是,几个组件需要同一份数据,你要么把状态提升到父组件(写更多代码),要么每个组件各自请求(浪费网络资源)。
说实话,那段时间我一度怀疑是不是我的姿势不对。后来发现SWR之后,那种”卧槽还能这样”的感觉,大概就跟第一次用Git替代手动复制文件夹差不多。三行代码,解决了80%的问题。不夸张。
今天就聊聊SWR——Vercel出品的React数据获取库。核心就一个词:stale-while-revalidate(陈旧-重新验证)。听起来很技术?其实原理超简单:先给你看缓存的”旧照片”(快),后台偷偷拍新的(准),拍完了悄悄换上(无感)。
为什么需要SWR?传统数据获取的三大痛点
我们先看看传统的React数据获取是什么样的。假设你要显示一个用户列表:
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}24行代码。只是为了显示一个列表。你可能觉得还行?那再想想这些场景:
痛点1:重复到想吐的模板代码
每个需要数据的组件都得这么写。用户列表要写,文章列表要写,评论列表还要写。三个状态、一堆if判断、错误处理…复制粘贴都觉得丢人。有次我数了数,一个中型项目里,光这种模板代码就占了20%的代码量。
痛点2:缓存?不存在的
最扎心的是没有缓存。用户从首页跳到详情页,再返回首页——又是漫长的loading。数据明明30秒前才拿过,却要重新请求。用户抱怨”你们这网站怎么这么慢”,其实不是API慢,是我们压根没缓存。
有人说可以自己写个全局状态管理啊。嗯,可以。然后你就要维护Redux/Zustand的store、action、reducer…原本3分钟能写完的功能,变成了30分钟的架构设计。
痛点3:多组件数据同步是噩梦
更麻烦的是多个组件用同一份数据。比如顶部导航要显示未读消息数,侧边栏也要显示,消息列表页还要显示。怎么办?状态提升到最顶层?那每次更新都要传十几层props。用Context?那每次消息更新,整个树都要re-render。
我记得有次改一个消息通知功能,明明逻辑很简单,却花了两天处理状态同步问题。提交代码的时候,满屏都是setState和useEffect,自己都看不下去。
这就是为什么SWR能火起来。它不是又一个轮子,而是真的解决了痛点。用SWR重写上面的代码?
import useSWR from 'swr';
function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}9行代码。搞定。而且自带缓存、自动revalidate、跨组件共享。对了,fetcher就是你的fetch函数,通常全局定义一次就够了:
const fetcher = url => fetch(url).then(r => r.json());这差距,一目了然。
SWR的核心概念:Stale-While-Revalidate策略
SWR这个名字来自HTTP RFC 5861定义的缓存失效策略。别被”RFC”吓到,原理超好理解。
用照片打个比方:你想看朋友的近照。传统方式是——打电话让他拍一张发给你,你等着。SWR的方式是——先翻出上次见面时拍的老照片(可能是上周的),边看边给他发消息让他拍新的发过来,收到新照片后再替换。
关键差别在哪?你不用干等。立即能看到内容(虽然可能有点旧),同时确保最终看到的是最新的。这就是”stale-while-revalidate”——展示旧的,同时重新验证。
SWR的完整工作流程:
第一步:立即返回缓存(stale)
- 组件挂载时,SWR先检查本地缓存
- 有缓存?立即返回,页面秒开
- 没缓存?返回undefined,显示loading
第二步:后台请求(revalidate)
- 无论是否有缓存,都会发起API请求
- 用户感知不到,因为已经在看内容了
第三步:更新数据
- API返回后,悄悄更新缓存和UI
- 如果数据变了,React自动re-render
- 如果没变,什么都不做
来看个实际例子。我在做一个股票价格展示页面:
function StockPrice({ symbol }) {
const { data, error } = useSWR(`/api/stock/${symbol}`, fetcher);
return (
<div>
<h2>{symbol}</h2>
<p>价格: {data ? `$${data.price}` : 'Loading...'}</p>
<span>更新时间: {data?.updatedAt}</span>
</div>
);
}用户第一次打开页面,data是undefined,显示”Loading…”。一秒钟后API返回,显示价格。
用户切到别的tab看了会邮件,10分钟后切回来——瞬间就看到价格(来自缓存),loading都没闪。但与此同时,SWR已经在后台发起新请求。如果股价变了,页面会自动更新;没变就保持原样。
这个体验,丝滑。
自动revalidation的三种时机:
SWR默认会在这些情况下自动重新获取数据:
- 组件重新挂载(revalidateOnMount):刷新页面或路由切换回来时
- 窗口重新聚焦(revalidateOnFocus):用户切回浏览器tab时
- 网络恢复(revalidateOnReconnect):断网后重新联网时
这意味着什么?你啥都不用管,SWR自动帮你保持数据新鲜。用户看邮件看了半小时,切回你的网站——SWR自动刷新数据。用户在地铁里断网了,出站恢复4G——SWR自动重新加载。
说实话,第一次知道这些是默认行为时,我直接惊了。以前这些场景要么不处理(用户看到过期数据),要么手动监听visibilitychange、online事件,写一堆代码。现在?免费送的。
关键的key概念:
你可能注意到了,useSWR的第一个参数是'/api/users'这样的字符串。这个叫”key”,非常重要。
// 两个组件用同一个key
function Header() {
const { data } = useSWR('/api/user', fetcher);
return <div>欢迎, {data?.name}</div>;
}
function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>个人资料: {data?.email}</div>;
}只要key相同,数据就会共享。SWR只会请求一次API,两个组件都拿到相同的数据,还会自动同步更新。
这就是为什么说它解决了”多组件数据同步”的问题。不需要状态管理,不需要Context,就是一个key的事儿。
缓存策略深入:让数据获取更智能
前面说了SWR会自动缓存、自动revalidate。但实际项目中,不同数据的”新鲜度要求”是不一样的。用户头像可能一周都不变,股票价格却要秒级更新。这时候就需要调整缓存策略。
默认行为很聪明,但不是万能的
SWR默认的配置其实挺激进的:
- 每次组件挂载都revalidate
- 每次窗口聚焦都revalidate
- 每次网络恢复都revalidate
对于实时性要求高的数据(聊天消息、在线状态),这很完美。但对于相对稳定的数据(文章列表、用户资料),就有点浪费了。用户在两个tab之间来回切换,每次都发请求?没必要。
核心配置选项
SWR提供了一堆配置项,但你真正常用的就这几个:
const { data } = useSWR('/api/articles', fetcher, {
revalidateOnFocus: false, // 窗口聚焦时不重新请求
revalidateOnReconnect: false, // 网络恢复时不重新请求
refreshInterval: 0, // 轮询间隔(毫秒),0表示不轮询
dedupingInterval: 2000, // 2秒内的相同请求会被去重
});这些配置项的名字很直白,基本看名字就懂。
场景1:实时数据(股票、在线人数)
const { data } = useSWR('/api/stock/AAPL', fetcher, {
refreshInterval: 1000, // 每秒轮询一次
revalidateOnFocus: true // 切回页面立即更新
});这种数据时效性要求高,恨不得时时刻刻都是最新的。轮询是最简单的方案(当然,更好的是用WebSocket,但那是另一个话题了)。
场景2:相对稳定数据(用户信息、文章列表)
const { data } = useSWR('/api/profile', fetcher, {
revalidateOnFocus: false, // 不要每次聚焦都刷新
refreshInterval: 0, // 不轮询
dedupingInterval: 60000, // 1分钟内不重复请求
});用户资料一般不会频繁变化。切换tab不需要刷新,节省请求。但如果用户主动修改了资料,你还是可以手动触发更新(后面讲mutate)。
场景3:几乎静态的内容(文档、帮助页)
const { data } = useSWR('/api/docs', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: false, // 连挂载时都不验证
revalidateIfStale: false, // 就算数据是stale的也不验证
});这种数据可能一个月都不会变。首次加载后,基本就是永久缓存了。除非用户手动刷新页面。
全局配置vs局部配置
如果整个应用的策略比较统一,可以用SWRConfig设置全局默认值:
import { SWRConfig } from 'swr';
function App() {
return (
<SWRConfig value={{
refreshInterval: 3000,
fetcher: (url) => fetch(url).then(r => r.json()),
revalidateOnFocus: false,
}}>
<Dashboard />
</SWRConfig>
);
}这样所有子组件的useSWR都会继承这些配置。当然,单个useSWR还可以override覆盖:
// 这个组件的revalidateOnFocus会是true,覆盖全局的false
const { data } = useSWR('/api/realtime', fetcher, {
revalidateOnFocus: true
});请求去重:节省你的API配额
有个很实用的功能——自动请求去重。假设你有三个组件同时挂载,都用useSWR('/api/user')获取用户数据。SWR不会发三次请求,而是只发一次,三个组件共享结果。
这个”去重窗口”默认是2秒(dedupingInterval: 2000)。也就是说,2秒内的相同请求会被合并。如果你的组件可能快速重新挂载(比如快速切换tab),可以把这个值调大一点。
说实话,这个功能救过我。之前有个列表页,用户快速滚动时会触发多次数据加载(我写的逻辑有点问题),结果同一个请求短时间内发了十几次。加上这个去重配置后,立刻就正常了。虽然治标不治本,但至少没把API配额用光(笑)。
条件fetching:依赖其他数据的请求
有时候你需要先获取A数据,再根据A的结果获取B。SWR有个小技巧——key传null就会暂停请求:
// 先获取用户信息
const { data: user } = useSWR('/api/user', fetcher);
// 等user加载完毕,再获取user的项目列表
const { data: projects } = useSWR(
user ? `/api/projects?userId=${user.id}` : null,
fetcher
);当user是undefined时,key是null,SWR不会发请求。等user加载完了,key变成有效值,SWR才会开始请求projects。串行依赖,就这么简单。
乐观更新:提升用户体验的秘密武器
缓存解决了”快”的问题,但还有个场景没覆盖到——用户操作。比如用户点了”点赞”,你是让他等API返回再显示红心?还是立即显示红心,后台偷偷发请求?
这就是乐观更新(Optimistic Update)的意义——假设操作会成功,立即更新UI,失败了再回滚。听起来有点冒险?但实际上大部分操作都会成功(除非网络问题或服务器挂了),用户体验提升是肉眼可见的。
对比一下传统方式和乐观更新
传统方式:
- 用户点击”点赞”
- 按钮变成loading状态(或者禁用)
- 等待500ms-2s(看网速)
- API返回成功,红心亮起
- 用户:“这网站有点慢啊”
乐观更新:
- 用户点击”点赞”
- 红心立即亮起(本地更新)
- 后台发送API请求
- (通常)成功,啥都不用做
- (偶尔)失败,红心恢复灰色,提示”操作失败”
后者的延迟是0ms。用户感觉?飞快。
mutate函数:手动控制缓存
SWR提供了mutate函数来手动更新缓存。最简单的用法:
import { mutate } from 'swr';
// 手动触发 /api/user 的重新验证
mutate('/api/user');但乐观更新需要更多控制。来看一个完整的Todo应用示例:
import useSWR, { mutate } from 'swr';
function TodoList() {
const { data: todos } = useSWR('/api/todos', fetcher);
const addTodo = async (text) => {
const newTodo = { id: Date.now(), text, completed: false };
// 核心:乐观更新配置
mutate(
'/api/todos',
async (currentTodos) => {
// 1. 立即在UI显示新Todo(乐观)
const optimisticData = [...currentTodos, newTodo];
// 2. 后台发送真实请求
const savedTodo = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
}).then(r => r.json());
// 3. 用真实数据替换临时数据
return [...currentTodos, savedTodo];
},
{
optimisticData: [...todos, newTodo], // 立即显示
rollbackOnError: true, // 失败时回滚
revalidate: false, // 不需要额外验证
}
);
};
return (
<div>
{todos?.map(todo => <div key={todo.id}>{todo.text}</div>)}
<button onClick={() => addTodo('New task')}>Add</button>
</div>
);
}这段代码有点长,但逻辑很清晰:
- 用户点击”Add”
- 立即在列表里显示新Todo(
optimisticData) - 后台发送POST请求
- 如果成功,用服务器返回的真实数据替换(可能有id、timestamp等字段)
- 如果失败,回滚到之前的状态(
rollbackOnError: true)
四大核心选项
mutate的options对象有四个关键配置:
1. optimisticData:立即更新的数据
optimisticData: [...todos, newTodo] // 或者传函数可以传固定值,也可以传函数。函数形式更灵活:
optimisticData: (currentTodos) => [...currentTodos, newTodo]2. populateCache:是否用返回值更新缓存
默认是true。大多数情况你希望用API返回的数据更新缓存(因为服务器可能添加了id、createdAt等字段)。但如果API返回的不是完整数据,可以设为false。
3. revalidate:是否重新验证
通常设为false。因为你已经手动更新了数据,不需要SWR再发一次请求。除非你不信任自己的更新逻辑,想让SWR再确认一遍。
4. rollbackOnError:失败时是否回滚
超级重要。设为true的话,如果mutate函数抛出错误,SWR会自动回滚到更新前的数据。用户点了”点赞”,红心亮了,但API失败了?SWR自动让红心变回灰色。
还可以传函数做更细粒度控制:
rollbackOnError: (error) => {
// 网络超时不回滚,其他错误回滚
return error.name !== 'AbortError';
}实战:删除Todo
删除操作也是乐观更新的经典场景:
const deleteTodo = async (id) => {
mutate(
'/api/todos',
async (currentTodos) => {
// 立即从列表中移除
const optimistic = currentTodos.filter(t => t.id !== id);
// 后台发送删除请求
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
// 返回更新后的数据
return optimistic;
},
{
optimisticData: todos.filter(t => t.id !== id),
rollbackOnError: true,
}
);
};用户点击删除,Todo立即消失。如果服务器说”这条Todo不存在”或者请求失败了?Todo又会出现,并显示错误提示。
useSWRMutation:更优雅的写法
SWR 2.0引入了专门的useSWRMutation Hook,代码更简洁:
import useSWRMutation from 'swr/mutation';
async function updateUser(url, { arg }) {
await fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
});
}
function Profile() {
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser);
return (
<button
onClick={() => trigger({ name: 'John' })}
disabled={isMutating}
>
Update Name
</button>
);
}trigger就是用来触发mutation的,isMutating告诉你请求是否正在进行。比手动管理loading状态舒服太多。
什么时候该用乐观更新?
不是所有操作都适合乐观更新。经验法则:
✅ 适合乐观更新:
- 点赞/收藏(失败率低,可逆)
- 添加/删除列表项(用户立即看到结果更重要)
- 开关状态切换(像切换通知设置)
- 表单输入保存(草稿功能)
❌ 不适合乐观更新:
- 支付操作(必须等确认)
- 删除账户(不可逆,必须慎重)
- 敏感数据修改(密码、权限)
- 需要服务器计算的操作(比如”生成报告”,你不知道结果长啥样)
原则就是:操作失败率低、可逆、用户体验优先的场景,用乐观更新。关键业务逻辑、不可逆操作,老老实实等服务器返回。
说个我踩的坑。之前做一个评论功能,用了乐观更新。用户发评论,立即显示,体验很好。结果有天发现有用户在疯狂刷评论——因为他网络很慢,点了”发送”后没反应(其实已经发送了),他就连续点了十几次。每次点击都optimistic更新了,于是页面上出现了十几条相同的评论。虽然后台API有防重复逻辑,但前端UI还是乱了。
后来加了个isMutating判断,按钮在请求期间禁用,问题解决。教训就是:乐观更新很爽,但要考虑边界情况。
SWR vs React Query:如何选择?
聊了这么多SWR的好处,你可能会问:那React Query呢?网上搜”React数据获取”,React Query的文章也一大堆。到底选哪个?
坦白讲,这两个库都很优秀,2025年都还在积极维护。选错了不会死,但选对了能让开发更舒服。
体积对比:SWR更轻
- SWR: 5.3KB (gzip后)
- React Query (TanStack Query): 16.2KB
如果你在做一个对包体积特别敏感的项目(比如营销落地页、移动端H5),SWR的优势明显。但说实话,对于大多数项目,这10KB的差距没那么要命。
复杂度对比:SWR更简单
SWR的API设计极简,核心就一个useSWR Hook。5分钟能学会,10分钟能上手。
React Query功能更强大,但相应的学习曲线也更陡。你需要理解QueryClient、useQuery、useMutation、queryKeys、cache time vs stale time等概念。文档很长。
如果团队技术栈比较新(或者前端经验不多),SWR更友好。
功能对比:React Query更全面
React Query有几个SWR没有的功能:
官方DevTools:可视化查看所有query状态、缓存内容、refetch周期。调试体验超棒。SWR没有官方DevTools。
更细粒度的缓存控制:可以分别设置cache time(数据在内存中保留多久)和stale time(数据多久会变stale)。SWR相对简单,就是cache+revalidate。
paginated/infinite queries:React Query有专门的
useInfiniteQuery,SWR是useSWRInfinite。功能差不多,但React Query的API更成熟一些。mutation支持更强:React Query的
useMutation设计更系统,有全局mutation state、retry策略、onSettled回调等。SWR的useSWRMutation是2.0才加的,功能相对简单。
社区和生态
React Query(现在叫TanStack Query)的社区更大,NPM周下载量也更多。你遇到问题,StackOverflow上找答案更容易。
SWR是Vercel出品,和Next.js是一家。官方文档对Next.js有专门的集成指南,如果你用Next.js,SWR是”亲儿子”。
我的选择建议
| 选SWR | 选React Query |
|---|---|
| 项目相对简单,数据获取逻辑不复杂 | 项目复杂,需要精细化控制缓存 |
| 包体积敏感(移动端) | 不在乎多10KB |
| 用Next.js | 用其他框架(CRA、Vite等) |
| 团队前端经验不多 | 团队熟悉复杂库,喜欢丰富功能 |
| 想要快速上手 | 愿意花时间学习完整生态 |
我自己的经验?小项目或MVP阶段,首选SWR,简单快速。项目大了、需求复杂了,可能会迁移到React Query。但说实话,大多数项目SWR完全够用。
还有一个点:两者迁移成本其实不高。核心概念都差不多(cache、revalidate、mutation),如果哪天真的需要React Query的高级功能,切换过去也不会太痛苦。不用过度纠结。
Next.js与SWR:天作之合
SWR和Next.js都是Vercel的产品,天生就配合得很好。如果你用Next.js,有几个特别方便的集成点。
App Router中使用SWR
Next.js 13+的App Router默认是服务器组件(Server Components)。SWR是客户端库,所以需要在Client Component中使用:
'use client'; // 必须标记为客户端组件
import useSWR from 'swr';
export default function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>{data?.name}</div>;
}记得加'use client'。这是新手常忘的。
SSR + SWR:配合fallback data
Next.js的强项是服务器端渲染(SSR)。你可以在getStaticProps或getServerSideProps里获取初始数据,然后传给SWR作为fallback:
// pages/profile.js
export async function getStaticProps() {
const user = await fetch('https://api.example.com/user').then(r => r.json());
return {
props: {
fallback: {
'/api/user': user // key对应SWR的key
}
},
revalidate: 60 // ISR: 每60秒重新生成
}
}
export default function Profile({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}
function UserProfile() {
// 首次渲染会用fallback数据,无需loading
const { data } = useSWR('/api/user', fetcher);
return <div>{data.name}</div>;
}这样用户首次访问就能看到完整内容(SEO友好),同时SWR会在客户端接管后继续revalidate。两全其美。
预取(Prefetching)
对于重要的数据,可以在用户还没点击的时候就预先加载:
import { mutate } from 'swr';
function ArticleLink({ id }) {
const prefetch = () => {
mutate(`/api/article/${id}`, fetch(`/api/article/${id}`).then(r => r.json()));
};
return (
<Link href={`/article/${id}`} onMouseEnter={prefetch}>
Read more
</Link>
);
}用户鼠标悬停在链接上时,就开始加载文章数据。等他点击时,数据可能已经在缓存里了,页面瞬间打开。
无限滚动:useSWRInfinite
做列表页最常见的需求——滚动加载更多:
import useSWRInfinite from 'swr/infinite';
function ArticleList() {
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null; // 没有更多了
return `/api/articles?page=${pageIndex + 1}&limit=10`;
};
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);
const articles = data ? data.flat() : [];
const isLoadingMore = isLoading || (size > 0 && data && typeof data[size - 1] === 'undefined');
return (
<div>
{articles.map(article => (
<div key={article.id}>{article.title}</div>
))}
<button
onClick={() => setSize(size + 1)}
disabled={isLoadingMore}
>
{isLoadingMore ? 'Loading...' : 'Load More'}
</button>
</div>
);
}setSize(size + 1)就会加载下一页。SWR会缓存每一页的数据,用户往回滚也不需要重新加载。
性能优化建议
在Next.js中用SWR,有几个优化要注意:
合理设置revalidate频率
- 静态内容(文档、帮助):关闭自动revalidate
- 用户相关数据:保持默认的focus revalidate
- 实时数据:用
refreshInterval轮询
API路由缓存
- Next.js的API路由可以配合
Cache-Controlheader - SWR会尊重HTTP缓存头
- Next.js的API路由可以配合
避免瀑布请求
- 如果多个数据有依赖关系,考虑在服务器端合并请求
- 或者用Next.js的并行数据获取
说个我的实际案例。之前做一个博客站,文章列表页用了SWR + ISR(Incremental Static Regeneration)。首屏是静态生成的HTML(秒开),用户看到内容的同时SWR在后台check有没有新文章,有的话自动更新列表。体验特别丝滑,而且SEO完美。
这就是Next.js + SWR的魅力——静态站点的性能 + 动态应用的实时性。
结论
写了这么多,回头总结一下。
SWR解决了React数据获取的三大痛点:代码冗余、缺乏缓存、多组件同步。核心策略”stale-while-revalidate”既快(立即显示缓存)又准(后台更新)。加上自动revalidation、请求去重、乐观更新这些功能,基本覆盖了90%的数据获取场景。
如果让我总结几个最佳实践:
- 根据数据特性调整缓存策略:实时数据轮询,稳定数据长缓存
- 对用户操作使用乐观更新:前提是失败率低、可逆
- 善用key做数据共享:不要为了共享数据引入状态管理
- Next.js项目优先选SWR:配合fallback data做SSR
- 项目简单选SWR,复杂选React Query:不要过度设计
最后说句实话:SWR不是银弹。它解决不了糟糕的API设计,也代替不了合理的架构。但如果你的后端API已经设计得不错,SWR能让前端代码优雅很多很多。
下一个项目,试试SWR?相信我,用完你会回来感谢我的。
SWR完整使用流程
从安装到配置、使用、优化缓存的完整步骤
⏱️ 预计耗时: 1 小时
- 1
步骤1: 安装和基础使用
安装:
```bash
npm install swr
```
基础使用:
```tsx
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher)
if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>
return <div>{data.map(user => <div key={user.id}>{user.name}</div>)}</div>
}
```
关键点:
• 使用'use client'标记为客户端组件
• fetcher函数处理数据获取
• useSWR返回data、error、isLoading - 2
步骤2: 配置全局选项
使用SWRConfig:
```tsx
'use client'
import { SWRConfig } from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function Providers({ children }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshInterval: 0,
}}
>
{children}
</SWRConfig>
)
}
```
常用选项:
• revalidateOnFocus:窗口聚焦时重新验证
• revalidateOnReconnect:网络重连时重新验证
• refreshInterval:定时刷新(0表示不刷新)
• dedupingInterval:去重间隔(默认2000ms)
关键点:在根组件配置,所有子组件共享配置 - 3
步骤3: Next.js集成(SSR)
配合fallback data:
```tsx
// app/users/page.tsx(Server Component)
import { getUsers } from '@/lib/users'
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx(Client Component)
'use client'
import useSWR from 'swr'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers // 使用SSR数据作为初始值
})
return <div>{data.map(...)}</div>
}
```
优势:
• 首屏数据来自SSR,加载快
• 后续更新用SWR,体验好
• 结合SSR和客户端缓存的优势
关键点:使用fallbackData而不是initialData(fallbackData不会触发重新验证) - 4
步骤4: 乐观更新
使用mutate做乐观更新:
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
// 乐观更新:立即更新UI
mutate(`/api/users/${id}`, { ...user, name }, false)
// 发送请求
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
// 重新验证:确保数据正确
mutate(`/api/users/${id}`)
}
```
关键点:
• mutate的第三个参数false表示不重新验证
• 请求成功后再次mutate重新验证
• 用户体验更好(立即看到更新)
常见问题
SWR是什么?为什么需要它?
传统数据获取痛点:
• 每个数据获取都要写useState、useEffect、loading、error处理
• 20行代码打底
• 多个组件需要同一份数据时要么状态提升要么各自请求
SWR优势:
• 自动缓存:相同key的请求自动共享缓存
• 自动重新验证:窗口聚焦、网络重连时自动刷新
• 自动去重:相同请求只发一次
• 自动错误重试:请求失败自动重试
• 一个Hook简化90%的数据获取代码
三行代码解决80%的问题:
```tsx
const { data, error, isLoading } = useSWR('/api/users', fetcher)
```
SWR和React Query有什么区别?
• 简单轻量,学习成本低
• 适合大多数项目
• 功能相对简单
• Vercel出品,Next.js集成好
React Query:
• 功能强大,功能丰富
• 适合复杂场景
• 学习成本高
• 更灵活,但配置更复杂
选择建议:
• 项目简单 → SWR
• 项目复杂 → React Query
• 不确定 → 先用SWR,真不行再换
关键点:不要过度设计,项目简单选SWR,复杂选React Query。
SWR怎么在Next.js中使用?
```tsx
// app/users/page.tsx(Server Component)
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx(Client Component)
'use client'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers // 使用SSR数据
})
return <div>{data.map(...)}</div>
}
```
优势:
• 首屏数据来自SSR,加载快
• 后续更新用SWR,体验好
• 结合SSR和客户端缓存的优势
关键点:
• 使用fallbackData而不是initialData
• fallbackData不会触发重新验证
• 适合Next.js App Router
SWR的缓存策略是什么?
流程:
1. 首次请求:显示loading
2. 请求成功:显示数据并缓存
3. 再次请求:立即显示缓存数据(快)
4. 后台重新验证:悄悄更新数据(准)
5. 更新完成:悄悄换上新数据(无感)
关键特性:
• 自动缓存:相同key自动共享
• 自动去重:相同请求只发一次
• 自动重新验证:窗口聚焦、网络重连时刷新
• 自动错误重试:请求失败自动重试
配置选项:
• revalidateOnFocus:窗口聚焦时重新验证
• revalidateOnReconnect:网络重连时重新验证
• refreshInterval:定时刷新
• dedupingInterval:去重间隔
优势:用户体验好,数据总是最新的,但不会看到loading。
如何实现乐观更新?
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
// 乐观更新:立即更新UI
mutate(`/api/users/${id}`, { ...user, name }, false)
// 发送请求
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
// 重新验证:确保数据正确
mutate(`/api/users/${id}`)
}
```
关键点:
• mutate的第三个参数false表示不重新验证
• 请求成功后再次mutate重新验证
• 用户体验更好(立即看到更新)
注意:如果请求失败,需要回滚到原始数据。
SWR适合什么场景?
• 需要频繁更新的数据(用户列表、通知等)
• 多个组件需要同一份数据
• 需要自动缓存和重新验证
• Next.js项目(集成好)
不适合的场景:
• 简单的静态数据(不需要缓存)
• 只需要一次请求的数据(不需要重新验证)
• 复杂的缓存需求(用React Query)
选择建议:
• 大多数项目 → SWR
• 复杂场景 → React Query
• 简单数据 → 直接用fetch
关键点:不要为了用SWR而用SWR,根据实际需求选择。
20 分钟阅读 · 发布于: 2025年12月19日 · 修改于: 2026年1月22日
相关文章
Next.js 电商实战:购物车与 Stripe 支付完整实现指南

Next.js 电商实战:购物车与 Stripe 支付完整实现指南
Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战

Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战
Next.js 单元测试实战:Jest + React Testing Library 完整配置指南


评论
使用 GitHub 账号登录后即可评论