简介
“SWR“ 这个名字来自于 stale-while-revalidate:一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
一个用于请求远程数据的 React Hooks 库,官网的快速开始示例如下:
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
上述示例是一个最基础的前端基础请求例子, useSWR 接受一个 key 和一个异步请求函数 fetcher 作为参数。 key 是数据的唯一标识符,通常是 API URL 的路径,并且 fetcher 接受 key 作为其参数,完成具体的数据请求行为并异步返回数据。
useSWR 可以从返回获取3个值: data、error 和 isLoading ,分别对应请求可能的3种状态:“ready”、“error”或“loading”,可以使用 data、error 和 isLoading 的值来确定当前的请求状态,并返回相应的 UI。其中第二个参数 fetcher 可以是任何返回数据的异步函数,你可以使用原生的 fetch 或 Axios 之类的工具。
功能
SWR 相比常见的数据请求库提供了很多很酷且很有脑洞的特性,比如导航切换时使用缓存数据进行优先渲染然后在进行对比更新,数据在 focus 时更新,轮询检查更新,分页按需更新等等。
- 快速页面导航
- 间隔轮询
- 数据依赖
- 聚焦时重新请求
- 网络恢复时重新请求
- 本地缓存更新 (Optimistic UI)
- 智能错误重试
- 分页和滚动位置恢复
- React Suspense
聚焦时重新请求
当你重新聚焦一个页面或在标签页之间切换时,SWR 会自动重新请求数据。
这个功能非常实用,可以保持网站同步到最新数据。对于在长时间位于后台的标签页,或 休眠 的电脑等情况下刷新数据也很有帮助。
使用聚焦时重新验证在页面间自动同步登陆状态。
该特性默认是启用的。你可以通过 revalidateOnFocus 选项禁用它。
定期重新请求
在很多情况下,数据会因为多个设备、多个用户、多个选项卡而发生改变。那么我们如何随着时间的推移更新屏幕上的数据呢?
SWR 会为你提供自动重新请求数据的选项。这很 智能,意味着只有与 hook 相关的组件 在屏幕上 时,才会重新请求。
当用户进行更改时,两个会话最终将呈现相同的数据。
默认需要通过配置来启用此功能,你可以通过设置 refreshInterval 值来启用它:
useSWR('/api/todos', fetcher, { refreshInterval: 1000 })
还有其他选项,例如 refreshWhenHidden 和 refreshWhenOffline。这两项默认都是禁用的,所以当网页不在屏幕上或没有网络连接时,SWR 不会请求。
按需请求
使用 null 或传一个函数作为 key 来有条件地请求数据。如果函数抛出错误或返回 falsy 值,SWR 将不会启动请求。
// 有条件的请求
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
// ...或返回一个 falsy 值
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher)
// ... 或在 user.id 未定义时抛出错误
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)
依赖请求
SWR 还允许请求依赖于其他数据的数据。当需要一段动态数据才能进行下一次数据请求时,它可以确保最大程度的并行性(avoiding waterfalls)以及串行请求。
function MyProjects () {
const { data: user } = useSWR('/api/user')
const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id)
// 传递函数时,SWR 会用返回值作为 `key`。
// 如果函数抛出错误或返回 falsy 值,SWR 会知道某些依赖还没准备好。
// 这种情况下,当 `user`未加载时,`user.id` 抛出错误
if (!projects) return 'loading...'
return 'You have ' + projects.length + ' projects'
}
数据更改&重新验证(强制刷新)
有些时候可能我们想自己控制数据刷新时机,这时可以使用 hook 暴露出的 mutate 函数。
当你调用 mutate(key)(或者只是使用绑定数据更改 API mutate())时没有传入任何数据,它会触发资源的重新验证(将数据标记为已过期并触发重新请求)。这个例子展示了当用户点击 “Logout” 按钮时如何自动重新请求登陆信息。
import useSWR, { useSWRConfig } from 'swr'
function App () {
const { mutate } = useSWRConfig()
return (
<div>
<Profile />
<button onClick={() => {
// 设置 cookie 为已过期
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
// 通知所有拥有这个 key SWR 重新验证
mutate('/api/user')
}}>
Logout
</button>
</div>
)
}
useSWR 的优势
简化数据获取逻辑
useSWR 提供了一个简洁的接口,帮助你轻松地进行数据获取和管理。通过使用这个库,你可以在组件中使用一行代码来处理数据获取,而无需手动管理各种状态和生命周期方法。
const { data, error, isLoading } = useSWR('/api/user', fetcher)
自动缓存管理
useSWR 自动处理数据的缓存,以及在需要时重新获取数据。减轻了你在手动管理数据缓存和更新方面的工作负担。缓存是根据请求的键(key)进行管理的,这使得在多个组件中共享相同数据的情况变得更加容易。
数据响应性
当数据发生变化时,useSWR 会自动触发组件的重新渲染。这使得你可以很容易地保持 UI 与数据的同步,无需手动处理数据变化时的更新逻辑。
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
支持自定义配置
useSWR 允许你通过配置参数来定制数据获取的行为,例如定义重新获取数据的策略、设置缓存过期时间等。这为开发提供了更大的灵活性,以满足不同场景的需求。支持单独配置和全局配置。
// 单独配置
const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, options)
// 全局配置
<SWRConfig value={options}>
<Component/>
</SWRConfig>
与其他请求 hooks 的比较
比较当下最热门的 useRequest、useSWR 和 react-query,虽然在细节上分别有不同的实现,但是大体功能上都具备了下述 feature,可以看作是社区的最佳实践。
状态更简单:Effect 状态封装
在代码层面,现在的请求库基本已经将 loading 状态封装到 hooks 中。只要你触发了请求,只需要关心 hooks 中暴露出的 data 和 error 以及 loading 状态。这一点,无论是 useRequest 和 swr 都做了通用的封装:
// ahooks 的 useRequest
const { data, error, loading } = useRequest(fetcher);
// swr
const { data, error, isValidating } = useSwr('/getList', fetcher);
缓存更简单:cacheKey 机制
针对一个场景:例如你在某购物 App 商品列表页进入一个详情页页面,然后再返回。一般情况下,App 都会保留刚刚的搜索数据。但是在 Web App 中,往往页面需要重新发起请求,获取最新的列表。如果在弱网环境下,用户返回列表面临页面空白和等待加载的场景将会非常明显;即使网络情况较好,也会因为数据的重新请求,导致页面产生闪烁,当然有时候我们会用 loading 状态处理。
如果我们从用户体验的角度来看,刚刚的数据也许没有过期,或者我希望返回时能看到我刚刚看到的数据,这样的体验将会更加“顺滑”。如果我们能将前页的数据缓存储存起来同时去更新数据,再由 react 的 diff 机制去更新页面,这样便可以无缝的更新数据。
在 useRequest 中,可以为请求设置了一个 cacheKey 的字段,在需要重新拉取数据时,先读取缓存数据,再发起数据请求。而 SWR 和 react-query 的机制则更为激进,请求的 path 即是 key,针对这个 key 发起的请求将会被自动缓存。
const { data, error, loading } = useRequest(getList, {
cacheKey: 'list',
});
const { data, error, isValidating } = useSwr('/getList' /** path 即是 cacheKey */, TodoService);
编辑更简单:mutate 机制
先拉取服务端数据,进行编辑再将数据更新到服务端,是一个比较常见的场景,例如用户资料编辑的场景。传统的编辑逻辑,是在用户编辑后,实时将结果传送到服务端,再从服务端拉取编辑后的结果。如果在网络不好的场景下,会造成编辑有严重的滞后感。而在 SWR 中,编辑的思路为:通过 mutate 优先更改本地数据,然后将本地数据发送到服务端,最后从服务端拉取“验证”结果。这样,即使在网络较慢的场景下,也可以让用户很顺滑地编辑数据。
const { data, error, isValidating } = useSwr('/getList', TodoService);
return (
<div>
{isValidating && <span className="spin" />}
{data}
{error}
<button
onClick={() => {
mutate('/getList', 'local edit', false);
}}
>
mutate
</button>
</div>
)