Open gnosis23 opened 3 years ago
useSWR 的第一个参数作为本地缓存的 key 值,相同的 key 会访问到相同的缓存
// key可以是字符串
const { data } = useSWR('/api/user');
// key也可以是函数
const { data } = useSWR(() => '/api/user')
// key也可以是 object 或者 array
const { data } = useSWR(['/api/user', 'test', 1], fetcher);
里面用了一套 hash 算法,可以 hash 基本类型、数组、字符串等等,比如
['/api/user'] => '@"/api/user",'
[{ x: 1 }] => '@#x:1,,'
// 嵌套的 object,递归调用 hash 算法
[{ x: { y: 2 }, z: 3, u: {v: 4} }] => '@#z:3,x:#y:2,,u:#v:4,,,'
而对于一些无法序列的值,如 () => {}
, class {}
,用一个 id 替代,然后用 WeakMap 存储 object -> id 的索引
当请求返回后,组件已经被卸载了,这时候 setState 会显示警告。swr里面用了一个 unmountedRef 来保护
const unmountedRef = useRef(false)
const revalidate = useCallback((...) => {
if (!unmountedRef.current) return;
}, []);
useIsomorphicLayoutEffect(() => {
unmountedRef.current = false
return () => {
unmountedRef.current = true
}
}, [key, revalidate]);
假设两个组件里都发出相同的请求(key相同),那么就不应该发两个请求。
swr里面用了 2 个 map 来保存相同引用,一个用来存储 fetcher 返回的 promise,另一个用来存储 fetcher 发送的时间戳。 至于为什么要搞个时间戳,是因为有时需要请求去重dedupe,有时不需要去重(手动mutate)。
const revalidate = useCallback((...) => {
// ...
if (shouldStartNewRequest) {
CONCURRENT_PROMISES_TS[key] = getTimestamp();
CONCURRENT_PROMISES[key] = fn(...fnArg);
}
if (CONCURRENT_PROMISES_TS[key] !== startAt) {
// ...
return false;
}
}, []);
请求发送失败时,自动重连,默认8次
只有收集的依赖变化了以后,才会触发组件重新渲染
return {
trigger,
reset,
get data() {
// 使用 get 收集依赖
stateDependencies.data = true
return currentState.data
},
get error() {
stateDependencies.error = true
return currentState.error
},
get isMutating() {
stateDependencies.isMutating = true
return currentState.isMutating
}
}
const setState = useCallback(
(payload: Partial<S>) => {
let shouldRerender = false
const currentState = stateRef.current
for (const _ in payload) {
const k = _ as keyof S
// If the property has changed, update the state and mark rerender as
// needed.
if (currentState[k] !== payload[k]) {
currentState[k] = payload[k]
// If the property is accessed by the component, a rerender should be
// triggered.
// 收集依赖后才会触发
if (stateDependenciesRef.current[k]) {
shouldRerender = true
}
}
}
if (shouldRerender && !unmountedRef.current) {
if (IS_REACT_LEGACY) {
rerender({})
} else {
;(React as any).startTransition(() => rerender({}))
}
}
},
[rerender]
)
非官方:throw 一个 promise 可以让 suspense 转为 fallback 。
// change window.a to 1
const Mock = () => {
const a = useSyncExternalStore(
(cb) => { setInterval(cb, 1000) },
() => window.a,
);
if (!a) throw new Promise.resolve(true);
return <div>{a}</div>
}
// b
(
<Suspense fallback={<div>loading</div>}>
<Mock />
</Suspense>
)
后续 React 会出一个 use
来支持,上面的方法是不稳定的。
在使用了一段时间的 swr 后,它已经加入了“我的最爱”工具库了😂
下面结合我的个人经验,谈谈为什么你应该使用它。
接口请求状态
在请求接口的时候,一般都需要维护请求的状态。比如加载列表的时候,通常都需要区分下 loading、empty、success、error 等状态,然后更新相关的视图,看上去就是下面的代码
上面的代码缺陷很多:首先是模板代码多,维护麻烦;第二是容易出错,比如忘记重置相关变量、没考虑竞争等等问题。
而 swr 里面就几行代码:
请求先缓存后更新
swr 在发起请求的时候,会优先使用缓存的数据,然后当请求返回的时候更新视图。
为什么这个功能很重要呢?还是用上面那个列表接口作为例子,当用户第二次请求接口的时候(比如从某个页面返回),直接就能看到列表里的数据,然后过一会刷新成最新数据,在体验上就感觉很流畅。
如果某个接口更新不是很频繁,那么第二次请求的结果还是原来的数据,用户根本感觉不到有请求的过程,这就很爽😄。
还有个额外的好处,当用户返回之前的页面时,浏览器还能保留滚动位置。
请求依赖
条件加载,或者依赖加载:当前一个数据没有准备好的时候,就不会进行请求。这样就不用进行一堆条件判断了。
原理是当请求里面的函数抛错的时候,就不会继续请求。