SWR 用于数据请求的 React Hooks 库,SWR 由 Next.js(React 框架)背后的同一团队创建。
npm install swr
“SWR” 这个名字来自于 stale-while-revalidate:一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
使用 SWR,组件将会不断地、自动获得最新数据流。UI 也会一直保持快速响应。
import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json())
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 hook 接受一个字符串 key 和一个函数 fetcher。key 是数据的唯一标识符(通常是 API URL),并传递给 fetcher。fetcher 可以是任何返回数据的异步函数,你可以使用原生的 fetch 或 Axios 之类的工具。
基于请求的状态,这个 hook 返回 2 个值:data 和 error。
代码实现
了解了什么是 SWR 后,接下来看看如何实现它。
实现之前,先拆解下目标:
1.当请求数据时,首先从缓存中读取,并立即返回给调用者2.如果数据已经过期,则发起 fetch 请求,获取最新数据
我们需要用一个“容器”来缓存请求回来的复杂数据,在 JS 中,我们很容易第一时间想到使用 Object。
使用 Object 虽然没有什么问题,但它的结构是 “字符串—值” 的对应,只支持字符串作为键名。而在 ES6 中,Map 提供了 “值—值” 对应这种更完善的 Hash,更适合用于“键值对”这种数据结构。
为了方便代码实现后,有一个比较好的对比。这里先写一下不使用缓存时数据请求方式:
const data = await fetcher();
为了让 fetcher 支持数据缓存的能力,这里需要对 fetcher 进行一层封装。
封装之前,先定义一下需要被缓存的数据,那么什么数据需要被缓存呢?
很显然,不就是请求返回的数据吗。但与此同时,你也应该想到,如果重复调用函数,最好不要发送多次请求。
所以缓存数据中应该有:请求返回的数据,当前正在进行中的请求(如果有),避免多次请求。
const cache = new Map(); // 缓存数据
async function swr(cacheKey, fetcher) {
// 首先从缓存中获取
let data = cache.get(cacheKey) || { value: null, promise: null };
// 写入缓存
cache.set(cacheKey, data);
// 没有数据且也没有在请求中,需要发送请求
if (!data.value && !data.promise) {
// 保存当前请求的 promise
data.promise = fetcher()
.then((val) => {
data.value = val; // 请求成功,将数据存起来
})
.catch((err) => {
console.log(err);
})
.finally(() => {
data.promise = null; // 请求完毕,不再保存 promise
});
}
// 没有数据,但正在请求中,复用保存的 promise
if (data.promise && !data.value) await data.promise;
// 返回数据
return data.value;
}
这样,我们就实现了数据缓存的能力。
支持缓存过期时间,在已有缓存能力的基础上,再支持过期时间 cacheTime 就很容易了。只需要在发起新的请求前,判断下是否过期:
const isStaled = Date.now() - 获取到数据的时间 > cacheTime
所以,在缓存数据中我们还需要保存获取到数据的时间:
const cache = new Map();
// 新增 cacheTime 参数
async function swr(cacheKey, fetcher, cacheTime) {
let data = cache.get(cacheKey) || { value: null, time: 0, promise: null };
cache.set(cacheKey, data);
// 是否过期
const isStaled = Date.now() - data.time > cacheTime;
// 已经过期了,且也没有在请求中,需要发送请求
if (isStaled && !data.promise) {
data.promise = fetcher()
.then((val) => {
data.value = val;
data.time = Date.now(); // 保存获取到数据的时间
})
.catch((err) => {
console.log(err);
})
.finally(() => {
data.promise = null;
});
}
if (data.promise && !data.value) await data.promise;
return data.value;
}
有了以上的封装,调用方法变更为:
// before
const data = await fetcher();
// after
const data = await swr('cache-key', fetcher, 3000);
首次调用时,会通过接口请求数据。随后调用会立即返回缓存数据。如果调用间隔超过 3s,将先返回缓存数据,再请求接口获取最新的数据。
大功告成!我们用近 20 行代码简单实现了一套 SWR 机制。