SWR是什么
用于数据请求的 React Hooks 库。
SWR = stale-while-revalidate
一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
特性
- 极速、轻量、可重用的 数据请求
- 内置 缓存 和重复请求去除
- 实时 体验
- 支持TypeScript、React Native
场景
- 快速页面导航
- 间隔轮询
- 数据依赖
- 聚焦时重新验证
- 网络恢复时重新验证
- 本地缓存更新 (Optimistic UI)
- 智能错误重试
- 分页和滚动位置恢复
- React Suspense
语法
const { data, error } = useSWR('/api/user', fetcher)
参数:
- key是唯一标识符(string / function / array / null)
- 如果传入的是字符串,那么这个字符串就是序列化后的key
- 如果传入的是函数,那么执行这个函数,返回的结果就是序列化后的key
- 如果传入的是数组,那么通过 hash方法(类似hash算法,数组的值序列化后唯一)序列化后的值就是key
- fetcher(可选)是任何返回数据的异步函数
- options(可选)
返回:
- data
- error
- isValidating: 是否有请求或重新验证加载
- mutate
示例
场景:打开一个页面,获取用户信息,导航条显示“欢迎xxx”,用户信息展示用户的头像、性别等信息
正常Hooks的思路:Page页面,useEffect获取用户信息,传参给NaviBar组件显示“欢迎xxx”,传参给UserInfo组件展示头像等信息
// 页面组件
function Page() {
const [user, setUser] = useState(null)
// 请求数据
useEffect(() => {
fetch("/get/user_info")
.then((res) => res.json())
.then((data) => setUser(data))
}, [])
// 全局加载状态
if (!user) return null;
return (
<div>
<NaviBar user={user} />
<UserInfo user={user} />
</div>
)
}
// 导航条子组件
function NaviBar({ user }) {
return (
<div>
{`欢迎${user.name}`}
</div>
)
}
//用户信息子组件
function UserInfo({ user }) {
return <div>
<h1>{`性别:${user.}`}</h1>
<Avatar user={user}>
</div>
}
//子组件的子组件
function Avatar({ user }) {
return <img src={user.avatar} alt={user.name} />
}
这种写法的缺点是:
需要将所有的数据请求都保存在顶级组件中,并为树深处的每个组件添加 props。如果我们给页面添加更多的数据依赖,代码将变得更加难以维护。
SWR的写法:
// 页面组件(不请求数据)
function Page() {
return (
<div>
<NaviBar />
<CoUserInfontent />
</div>
);
}
// 导航条子组件(无需传参,内部获取)
function NaviBar() {
const { user, isLoading } = useUser();
return (
<div>
{`欢迎${user.name}`}
</div>
)
}
//用户信息子组件 (无需传参,内部获取)
function UserInfo() {
const { user, isLoading } = useUser();
return <div>
<h1>{`性别:${user.}`}</h1>
<Avatar user={user}>
</div>
}
//子组件的子组件 (无需传参,内部获取)
function Avatar({ user }) {
const { user, isLoading } = useUser();
return <img src={user.avatar} alt={user.name} />
}
//获取数据
const useUser = ({} = {}) => {
const url = '/get/user_info';
return useSWR(url, fetcher);
};
const fetcher = (url) =>{
return fetch("/get/user_info")
.then((res) => res.json())
.then((data) => setUser(data))
};
数据绑定到需要该数据的组件上,所有组件都是相互独立的。所有的父组件都不需要关心关于数据或数据传递的任何信息。它们只是渲染。现在代码更简单,更易于维护了。
只会有1个请求发送到API,因为它们使用相同的 SWR key,因此请求会被自动 去除重复、缓存 和 共享。
SWR 默认 深度比较 数据更改。如果 data 值没有改变,则不会触发重新渲染。
源码解读
- 合并参数config,优先级defaultConfig < Context.config < 自定义Config
- concurrent_promises( [kən'kʌrənt] 并发的 )变量用来保存所有需要并行的请求操作 判断是否已经存在,存在则读缓存或者等待结果,不存在则发起请求
- 请求过程中会判断是否超时,请求成功则cache.set保存缓存,然后broadcastState(key, newData, newState.error, false)用于让所有hooks都更新数据,失败后还会处理重试机制
- mutate 请求、重置时间戳
- 用的同一处缓存,当缓存更新的时候,会触发内部
notify接口通知到所有订阅了相关更新的处理函数,从而各个使用方可以监听到数据的变化,SWR对外暴露的状态是以响应式的方式进行处理,setState触发组件的自动更新。
使用时注意事项
1、请求参数的唯一性
2、缓存数据在被所有页面共享,要注意不能修改
- 从A页面获取了列表数据list, 但是A页面中只展示一个,list.slice(0, 1)
- 从B页面也获取list数据,此时获取到的list长度也只有1
3、不能通过SWR获取常量或者变量
4、不能中断请求
5、 key 对应的响应结果没有被删除,需要手动清理缓存,避免内存泄露