一. swr
介绍
swr
是React
数据请求的Hook
,提供数据缓存机制,减少重复请求。
javascript
import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then(res => res.json())
function App() {
const { data, isLoading } = useSWR('/api/user', fetcher)
if (isLoading) return <h1>Loading...</h1>
const { user } = data
return <h1>{user.name}</h1>
}
二. 实现swr
2.1 初始化cache
cache
是Map
类型数据,用于记录请求返回的数据,核心数据有data
、error
、isValidating
和isLoading
。
EVENT_REVALIDATORS
用于收集校验请求的方法。
FETCH
用于记录当前的请求实例,主要作用是减少重复请求和解决请求竞态问题。
javascript
export const SWRGlobalState = new WeakMap()
const initCache = provider => {
if (!SWRGlobalState.has(provider)) {
// key为调用useSWR方法传入的第一个入参对应的字符串标识, value是组件更新渲染方法
const subscriptions = {}
// 每次修改cache数据时,会校验cache数据是否变更,变更需要触发组件重新更新渲染
const setter = (key, value, prev) => {
provider.set(key, value)
const subs = subscriptions[key]
if (subs) {
subs.forEach(fn => fn(value, prev))
}
}
// 订阅组件更新渲染方法
const subscribe = (key, callback) => {
if (!subscriptions[key]) subscriptions[key] = []
const subs = subscriptions[key]
subs.push(callback)
return () => subs.splice(subs.indexOf(callback), 1)
}
const EVENT_REVALIDATORS = {}
const MUTATION = {}
const FETCH = {}
const PRELOAD = {}
SWRGlobalState.set(provider, [EVENT_REVALIDATORS, MUTATION, FETCH, PRELOAD, setter, subscribe])
}
return [provider]
}
const [cache] = initCache(new Map())
2.2 初始化默认配置
useSWR
支持许多的配置项,本文只讨论用到的配置,不做过多扩展。
javascript
export const defaultConfig = {
cache,
dedupingInterval: 2000, // 2s内相同请求会合并处理
}
2.3 定义useSWR
方法
首先需要将useSWR
第一个入参转换成对应的字符串标识,通过useSyncExternalStore
方法收集组件更新渲染方法。
接着在useEffect
回调方法请求接口数据,将请求实例记录到FETCH
对象中,避免重复请求,当请求接口返回数据后缓存到cache
中。
javascript
function useSWR(_key, fetcher, config) {
const { cache } = config
const [EVENT_REVALIDATORS, , FETCH, , setter, subscribe] = SWRGlobalState.get(cache)
// _key是任意数据类型,会转成成对应的字符串类型标识
const [key, fnArg] = serialize(_key)
// 只需关注data、error、isValidating、isLoading这四个字段的变化
const isEqual = (current, prev) => {
const keys = ['data', 'error', 'isValidating', 'isLoading']
for (const key of keys) {
if (!Object.is(current[key], prev[key])) return false
}
return true
}
// getter
const getCache = () => {
if (!cache.has(key)) {
cache.set(key, {
data: undefined,
error: undefined,
isValidating: true,
isLoading: true,
})
}
return cache.get(key)
}
// setter
const setCache = data => {
const prev = getCache()
setter(key, { ...prev, ...data }, prev)
}
// subscribe
const subscribeCache = callback =>
subscribe(key, (current, prev) => {
if (!isEqual(current, prev)) callback()
})
const cached = useSyncExternalStore(subscribeCache, getCache)
const revalidate = async () => {
if (!key) return
// 请求返回数据
let newData
// 请求标识
let timestamp
const cleanup = () => {
if (FETCH[key] && FETCH[key][1] === timestamp) delete FETCH[key]
}
const shouldStartNewRequest = !FETCH[key]
try {
if (shouldStartNewRequest) {
setCache({ isValidating: true, isLoading: !cached.data })
FETCH[key] = [fetcher(fnArg), getTimestamp()]
}
;[newData, timestamp] = FETCH[key]
// 获取请求数据
newData = await newData
if (shouldStartNewRequest) setTimeout(cleanup, config.dedupingInterval)
// 只要FETCH不存在对应请求实例或者请求标识变了,说明该请求属于过期请求,不需要处理其返回数据
if (!FETCH[key] || FETCH[key][1] !== timestamp) {
return
}
setCache({
data: newData,
error: undefined,
isValidating: false,
isLoading: false,
})
} catch (error) {
cleanup()
setCache({ error, isValidating: false, isLoading: false })
}
}
useEffect(() => {
if (!EVENT_REVALIDATORS[key]) EVENT_REVALIDATORS[key] = []
const revalidators = EVENT_REVALIDATORS[key]
revalidators.push(revalidate)
revalidate()
return () => {
const index = revalidators.indexOf(revalidate)
if (index >= 0) {
// O(1): faster than splice
revalidators[index] = revalidators[revalidators.length - 1]
revalidators.pop()
}
}
}, [key])
return { ...cached, mutate: internalMutate.bind(cache, key) }
}
export default (key, fetcher, config) =>
useSWR(key, fetcher, { ...defaultConfig, ...config })
2.4 定义mutate
方法
mutate
方法主要用于修改缓存数据以及触发校验请求方法,返回值为最新的cache
数据
javascript
const internalMutate = (cache, key, data) => {
const [EVENT_REVALIDATORS, , FETCH] = SWRGlobalState.get(cache)
const [getCache, setCache] = createCacheHelper(cache, key)
const startRevalidate = () => {
const revalidators = EVENT_REVALIDATORS[key]
// 将请求实例缓存清空掉确保能够重新发起请求
delete FETCH[key]
revalidators[0]().then(() => getCache().data)
}
// 如果data为undefined,直接重新校验请求即可
if (data === undefined) {
return startRevalidate()
}
setCache({ data })
startRevalidate()
return data
}
三. 总结
swr
通过cache
缓存请求数据,通过FETCH
缓存请求实例,减少重复请求,通过mutate
方法手动更新缓存数据,触发校验请求逻辑。代码仓库
创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!