持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情
这期主要讲useRequest这个库的用法以及源码实现,主要讲插件们的架构思路,介绍轮询、依赖更新、防抖节流、错误重试、缓存等功能和源码实现。
插件系统
useRequest
核心代码简单,高级功能都是通过插件式组织代码
插件就是钩子函数的集合。讲一下怎么实现的
举个例子 : useRequest(fn,{name:'xx'})
./plugins/useLoggerPlugin.js
function useLoggerPlugin(fetchInstance,{ name } ){
return {
onBefore(){}
onRequest(service, params) {}
onSuccess() {}
...
}
}
//onInit是用来初始化Fetch的初始状态的,在这个时候还没有fetchInstance
useLoggerPlugin.onInit = ({ name }) => {
console.log(name, 'onInit');
return { name };
}
export default useLoggerPlugin;
useRequest.js
import useRequestImplement from './useRequestImplement';
import useLoggerPlugin from './plugins/useLoggerPlugin';
function useRequest(service, options, plugins = []) {
return useRequestImplement(service, options,
// plugins 是自定义的插件 ,useLoggerPlugin是上面定义的插件
[...(plugins),
useLoggerPlugin,
]
);
}
export default useRequest;
useRequestImplement.js
function useRequestImplement(service, options, plugins) {
// options : {name:'xx'} ,plugins :[useLoggerPlugin]
const fetchOptions = { manual, ...rest };
。。。
const fetchInstance = useCreation(() => {
// 把 options 传给 插件们(useLoggerPlugin)初始化 得到插件初始化的数据,[name:'xx']
const initStates = plugins.map(p => p?.onInit?.(fetchOptions)).filter(Boolean);
//把每个插件返回的初始状态进行累加成一个最终的初始状态
return new Fetch(serviceRef, fetchOptions, update, Object.assign({}, ...initStates));
}, [])
fetchInstance.options = fetchOptions;
// 给每个插件传入参数,得到一个个的插件实例。
fetchInstance.pluginImpls = plugins.map(p => p(fetchInstance, fetchOptions));
。。。
fetch.js
class Fetch {
constructor(serviceRef, options, subscribe, initialState = {}) {
。。。//把初始化的值当成这里的默认值
this.state = {
loading: !options.manual, data: undefined, error: undefined,
params: undefined, ...initialState
};
}
// 执行 插件生命周期的方法。
runPluginHandler(event, ...rest) {
const r = this.pluginImpls.map(i => i[event]?.(...rest)).filter(Boolean);
return Object.assign({}, ...r);
}
async runAsync(...params) {
// 相当于把参数传给插件的onBefore方法去执行了
const { ...state } = this.runPluginHandler('onBefore', params);
// 请求是将 请求传给插件,插件处理后,如果没有返回请求方法,就用原来的,如果变了就用新的
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise
this.runPluginHandler('onSuccess', res, params);
。。。这些就不赘述了,在什么时候就执行什么插件的方法即可。
this.options.onError?.(error, params);
}
过程
1.创建插件,插件接收两个参数,
fetch
实例和配置项,有个静态初始化方法初始化配置,插件最终返回一个对象,里面有各种生命周期函数2.使用
useRequest
时,在创建fetch
实例前,会将所有插件初始化,得到处理后的初始化数据,在创建fetch
实例时当初始值,得到实例后作为参数传入插件,得到插件实例们3.在请求启动前,执行
onBefore
钩子,也要把参数传入插件实例们执行,返回的值当做fetch
实例的值。
onSuccess
等的时候也一样,onRequest
会比较特殊,传入请求方法,插件处理后,如果没有返回请求方法,就用原来的,如果变了就用新的
loading delay
通过设置 options.loadingDelay
,可以延迟 loading
变成 true
的时间,有效防止闪烁。
因为请求很快,你会闪现出一个加载中,这很没必要
原理就是请求前,使用 loading
变成 false
来覆盖原来的true
,让他隔 x 秒后 再设 loading
变成 true
useLoadingDelayPlugin.js
import { useRef } from 'react';
function useLoadingDelayPlugin(fetchInstance,
{ loadingDelay }) {
const timerRef = useRef();
if (!loadingDelay) {
return {};
}
const cancelTimeout = () => {
if (timerRef.current)
clearTimeout(timerRef.current);
}
return {
onBefore() {//在请求前设置一个定计器,在loadingDelay时间后变成true
timerRef.current = setTimeout(() => {
fetchInstance.setState({ loading: true });
}, loadingDelay);
// const { ...state } = this.runPluginHandler('onBefore', params);
// 这里返回false,this.setState({ loading: true, params, ...state });//fetch那里接收state并覆盖
return { loading: false };
},
onFinally() {
cancelTimeout();
},
onCancel() {
cancelTimeout();
}
}
}
export default useLoadingDelayPlugin;
useRequest.js
插件的引用
import useRequestImplement from './useRequestImplement';
import useLoggerPlugin from './plugins/useLoadingDelayPlugin';
function useRequest(service, options, plugins = []) {
return useRequestImplement(service, options,
// plugins 是自定义的插件 ,useLoggerPlugin是上面定义的插件
[...(plugins),
useLoadingDelayPlugin,
]
);
}
export default useRequest;
轮询
通过设置 options.pollingInterval
,进入轮询模式,useRequest
会定时触发 service 执行。
原理是请求结束的时候,设置定时器去请求。
还有这个参数
pollingWhenHidden | 在页面隐藏时,是否继续轮询。如果设置为 false,在页面隐藏时会暂时停止轮询,页面重新显示时继续上次轮询。 |
---|---|
usePollingPlugin.js
// 精髓版本
function usePollingPlugin(fetchInstance,
{ pollingInterval, pollingWhenHidden }) {
const timeRef = useRef();
onFinally() {
timeRef.current = setTimeout(() => {
fetchInstance.refresh();
}, pollingInterval);
},
// 完整版
// 加了 开始前和取消后 清除定时器、当pollingInterval为false时也去掉。
//对 pollingWhenHidden 的支持,
import { useRef } from 'react';
import useUpdateEffect from '../../../useUpdateEffect';
import isDocumentVisible from '../utils/isDocumentVisible';
import subscribeReVisible from '../utils/subscribeReVisible';
function usePollingPlugin(fetchInstance,
{ pollingInterval, pollingWhenHidden }) {
const timeRef = useRef();
const unsubscribeRef = useRef();
// 去除定时器,在开始前和结束后
const stopPolling = () => {
if (timeRef.current)
clearTimeout(timeRef.current);
// 去掉监听
unsubscribeRef.current?.();
}
// pollingInterval是允许为变量的,所以要判断,为false的时候要清除定时器
useUpdateEffect(() => {
if (!pollingInterval) {
stopPolling();
}
return () => console.log('stopPolling');
}, [pollingInterval]);
if (!pollingInterval) {
return {};
}
const isDocumentVisible = function() {
return document.visibilityState !== 'hidden';
}
return {
onBefore() {
stopPolling();
},
onFinally() {
//现在不是一定要开始定时器。而是要判断pollingWhenHidden参数
//如果设置为页面不可见的不轮询,并且页面不可见
if (!pollingWhenHidden && !isDocumentVisible()) {
//在此做一个订阅,订阅页面可见的事件,页面可见之后继续轮询
unsubscribeRef.current = subscribeReVisible(() => fetchInstance.refresh());
return;
}
// 请求结束后。设置定时器去请求。
timeRef.current = setTimeout(() => {
fetchInstance.refresh();
}, pollingInterval);
},
onCancel() {
stopPolling();
}
}
}
export default usePollingPlugin;
../utils/subscribeReVisible
// 一个订阅器 类似redux,订阅后,返回一个可以取消订阅的函数
import isDocumentVisible from './isDocumentVisible';
const listeners = []; // 存放监听事件
function subscribe(listener) {
listeners.push(listener);
return function () {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
// 当焦点恢复的时候,执行里面的监听事件
function revalidate() {
if (!isDocumentVisible()) return;
listeners.forEach(l => l());
}
window.addEventListener('visibilitychange', revalidate);
export default subscribe;
ready
useRequest 提供了一个 options.ready
参数,当其值为 false
时,请求永远都不会发出。
其具体行为如下:
- 当
manual=false
自动请求模式时,每次ready
从false
变为true
时,都会自动发起请求,会带上参数options.defaultParams
。 - 当
manual=true
手动请求模式时,只要ready=false
,则通过run/runAsync
触发的请求都不会执行
场景:触发了什么条件才允许请求
useAutoPlugin.js
import { useUpdateEffect } from 'ahooks';
import { useRef } from 'react';
function useAutoPlugin(fetchInstance,
{ ready = true, manual, defaultParams, refreshDeps = [], refreshDepsAction }) {
//是否已经自动运行过了
const hasAutoRun = useRef(false);
hasAutoRun.current = false;
// 当 `manual=false` 自动请求模式时,每次 `ready` 从 `false` 变为 `true` 时,都会自动发起请求,会带上参数 `options.defaultParams`。
useUpdateEffect(() => {
if (!manual && ready) {//如果当前是自动模式,并且ready变更为true
hasAutoRun.current = true;//设置已经自动运行过为true
fetchInstance.run(...defaultParams);
}
}, [ready]);
return {
// 阻止请求,返回stopNow
//当 `manual=true` 手动请求模式时,只要 `ready=false`,则通过 `run/runAsync` 触发的请求都不会执行
onBefore() {
if (!ready) {
return { stopNow: true }//跳过当前的执行,先不要发请求,因为我还没准备好
}
}
}
}
//用来设置初始状态state的 ready表示是否就绪,默认值是true,manual是是否是手动模式,默认是false.默认是自动模式
useAutoPlugin.onInit = ({ ready = true, manual = false }) => {
return { loading: !manual && ready }//如果是自动模式,并且ready=true,loading为true,相当 于会自动发请求
}
export default useAutoPlugin;
fetch.js
async runAsync(...params) {
// 当返回stopNow,阻止请求
const { stopNow, returnNow, ...state } = this.runPluginHandler('onBefore', params);
if (stopNow) {
return new Promise(() => { });
}
依赖更新
useRequest 提供了一个 options.refreshDeps
参数,当它的值变化后,会重新触发请求。
const [userId, setUserId] = useState('1');
const { data, run } = useRequest(() => getUserSchool(userId), {
refreshDeps: [userId],
refreshAction(){run(userId)}
});
还有个方法,refreshAction
,更新的时候,有传的时候调这个,没有就走 fetch.refresh
useAutoPlugin.js
// 加入这块代码 监听 refreshDeps 变化 如果是自动加载的,判断有无refreshDepsAction,有传的时候调这个,没有就走 `fetch.refresh`
// 这里的hasAutoRun.current其实我也不太懂
useUpdateEffect(() => {
if (hasAutoRun.current) {
return;
}
if (!manual) {
hasAutoRun.current = true;
if (refreshDepsAction) {
refreshDepsAction();
} else {
fetchInstance.refresh();
}
}
}, [...refreshDeps]);
屏幕聚焦重新请求
通过设置 options.refreshOnWindowFocus
,在浏览器窗口 refocus
和 revisible
时,会重新发起请求。
const { data } = useRequest(getUsername, {
refreshOnWindowFocus: true,
});
和 轮询 类似,订阅 visibilitychange
和 focus
focusTimespan | 重新请求间隔,单位为毫秒 | number | 5000 |
---|---|---|---|
useRefreshOnWindowFocusPlugin.js
import { useEffect, useRef } from 'react';
import subscribeFocus from '../utils/subscribeFocus';
import unUnMount from '../../../useUnmount';
import limit from '../utils/limit';
function useRefreshOnWindowFocusPlugin(fetchInstance,
{ refreshOnWindowFocus, focusTimespan }) {
const unsubscribeRef = useRef();
const stopSubscribe = () => {
unsubscribeRef.current?.();
}
useEffect(() => {
if (refreshOnWindowFocus) {
// limit 用于节流,防止多次请求。
const limitRefresh = limit(fetchInstance.refresh.bind(fetchInstance), focusTimespan)
unsubscribeRef.current = subscribeFocus(() => limitRefresh());
}
return () => {
stopSubscribe();
}
}, [refreshOnWindowFocus, focusTimespan]);
unUnMount(() => {
stopSubscribe();
})
return {}
}
export default useRefreshOnWindowFocusPlugin;
--------
// 一定间隔时间内不再执行
function limit(fn, timespan) {
let pending = false;
return (...args) => {
if (pending) return;
pending = true;
fn(...args);
setTimeout(() => pending = false, timespan);
}
}
export default limit;
subscribeFocus.js
import isDocumentVisible from './isDocumentVisible';
const listeners = [];
function subscribe(listener) {
listeners.push(listener);
return function () {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
function revalidate() {
if (!isDocumentVisible()) return;
listeners.forEach(l => l());
}
window.addEventListener('visibilitychange', revalidate);
window.addEventListener('focus', revalidate);
export default subscribe;
防抖
行为结束后一段时间再执行
通过设置 options.debounceWait
,进入防抖模式,此时如果频繁触发 run
或者 runAsync
,则会以防抖策略进行请求。
const { data, run } = useRequest(getUsername, {
debounceWait: 300,
manual: true
});
useDebouncePlugin.js
简单理解就是 fetchInstance.runAsync = debounce(fetchInstance.runAsync,debounceWait)
import { useEffect, useRef } from 'react';
function useDebouncePlugin(fetchInstance,
{ debounceWait }) {
const debounceRef = useRef();
useEffect(() => {
if (debounceWait) {
// 这里有点绕
// debounce(callback => callback(), debounceWait);中
// fn = callback => callback()
// 执行 runAsync ,即执行 debounceRef.current?.(() => originRunAsync(...args));
// 也就是 debounce(callback => callback(), debounceWait)(() => originRunAsync(...args))
// 进入定时器
// wait后,执行callback => callback()(...args),此时args=[() => originRunAsync(...args)]
// (callback) => callback()(...[() => originRunAsync(...args)] )
// callback = originRunAsync(...args)
// (() => originRunAsync(...args))=> () => originRunAsync(...args)()
// originRunAsync(...args)
// 简单理解就是 debounce(callback => callback(), debounceWait)(()=>x())
// fn = callback => callback(),fn(fn1)就是fn1(),也就是()=>x()(),就是x()
const originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
debounceRef.current = debounce(callback => callback(), debounceWait);
fetchInstance.runAsync = (...args) => {
return new Promise((resolve, reject) => {
debounceRef.current?.(() => originRunAsync(...args).then(resolve, reject));
});
}
}
}, [debounceWait])
return {
}
}
function debounce(fn, wait) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => fn(...args), wait);
}
}
export default useDebouncePlugin;
节流
一定时间执行一次,代码和防抖类似
通过设置 options.throttleWait
,进入节流模式,此时如果频繁触发 run
或者 runAsync
,则会以节流策略进行请求。
const { data, run } = useRequest(getUsername, {
throttleWait: 300,
manual: true
});
import { useEffect, useRef } from 'react';
function useDebouncePlugin(fetchInstance,
{ throttleWait }) {
const throttleRef = useRef();
useEffect(() => {
if (throttleWait) {
const originRunAsync = fetchInstance.runAsync.bind(fetchInstance);
throttleRef.current = throttle(callback => callback(), throttleWait);
fetchInstance.runAsync = (...args) => {
return new Promise((resolve, reject) => {
throttleRef.current?.(() => originRunAsync(...args).then(resolve, reject));
});
}
}
}, [throttleWait])
return {
}
}
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const currentTime = Date.now();
const nextTime = lastTime + interval;
if (currentTime >= nextTime) {
fn.apply(this, args);
lastTime = currentTime;
}
}
}
export default useDebouncePlugin;
错误重试
通过设置 options.retryCount
,指定错误重试次数,则 useRequest 在失败后会进行重试。
const { data, run } = useRequest(getUsername, {
retryCount: 3,
});
retryCount | 错误重试次数。如果设置为 -1 ,则无限次重试。 | number | - |
---|---|---|---|
retryInterval | 重试时间间隔,单位为毫秒。如果不设置,默认采用简易的指数退避算法,取 1000 * 2 ** retryCount ,也就是第一次重试等待 2s,第二次重试等待 4s,以此类推,如果大于 30s,则取 30s | number | - |
useRetryPlugin.js
// 成功、启动前、取消 初始化
//
import { useRef } from 'react';
function useRetryPlugin(fetchInstance,
{ retryCount, retryInterval }) {
const timerRef = useRef();///定时器,什么时候重试
const countRef = useRef();//计数器,重试的次数
const triggerByRetry = useRef(false);//布尔值,表示此请求是否是重新请求
if (!retryCount) {
return {};
}
return {
onBefore() {
if (!triggerByRetry.current) {
countRef.current = 0;
}
if (timerRef.current) {
clearTimeout(timerRef.current);
}
},
onSuccess() {
countRef.current = 0;
},
onError() {
countRef.current += 1;
//如果没有到达最大的重试次数retryCount=-1重试无数次
if (retryCount == -1 || countRef.current <= retryCount) {
//1s 2s 4s 8s 16s 32s
const timeout = retryInterval || Math.min(30000, 1000 * 2 ** countRef.current)
timerRef.current = setTimeout(() => {
triggerByRetry.current = true;
fetchInstance.refresh();
}, timeout);
} else {
countRef.current = 0;
}
},
onCancel() {
countRef.current = 0;
if (timerRef.current) {
clearTimeout(timerRef.current);
}
}
}
}
export default useRetryPlugin;
缓存
如果设置了 options.cacheKey
,useRequest
会将当前请求成功的数据缓存起来。下次组件初始化时,如果有缓存数据,我们会优先返回缓存数据,然后在背后发送新请求,也就是 SWR 的能力。
你可以通过 options.staleTime
设置数据保持新鲜时间,在该时间内,我们认为数据是新鲜的,不会重新发起请求。
你也可以通过 options.cacheTime
设置数据缓存时间,超过该时间,我们会清空该条缓存数据。
SWR
在组件第二次加载时,会优先返回缓存的内容,然后在背后重新发起请求。你可以通过点击按钮来体验效果。
那就需要个缓存。
cache.js
const cache = new Map();
// 存数据,解构应该是为了去除引用
const setCache = (key, cachedData) => {
cache.set(key, { ...cachedData });
}
const getCache = (key) => {
return cache.get(key);
}
export {
setCache,
getCache,
}
//key='cacheKey' key=['key1','key2']
useCachePlugin.js
import { useRef } from 'react';
import * as cache from '../utils/cache';
function useCachePlugin(fetchInstance,
{ cacheKey, staleTime = 0 }) {
const _getCache = (key) => {
return cache.getCache(key);
}
if (!cacheKey) {
return {}
}
return {
// 请求前 在缓存中查有没有这个key对应的数据
onBefore(params) {
const cachedData = _getCache(cacheKey, params);
// 找不到或者找到了这个对象但是没有data
if (!cachedData || !Object.hasOwnProperty.call(cachedData, 'data')) {
return {};
} else {
return {
data: cachedData.data
}
}
},
// 请求成功 后 将数据缓存
onSuccess(data, params) {
if (cacheKey) {
_setCache(cacheKey, {
data,//请求返回的数据
params,//请求的参数
time: new Date().getTime()//请求返回的时间
});
}
}
}
}
export default useCachePlugin;
fetch.js
没变
// 这里state得到data,用缓存进行加载。
const { ...state } = this.runPluginHandler('onBefore', params);
this.setState({ loading: true, params, ...state });//准备开始请求了
数据保质期
通过设置 staleTime
,我们可以指定数据新鲜时间,在这个时间内,不会重新发起请求
-1表示永不过期
原理:请求时需要检查是否到时间,没到时间就直接返回数据,不要请求
fetch.js
请求前判断是否数据新鲜,返回returnNow
,新鲜则返回 缓存数据 并 阻挡请求
const { stopNow, returnNow, ...state } = this.runPluginHandler('onBefore', params);
if (returnNow) {
return Promise.resolve(state.data);
}
useCachePlugin.js
function useCachePlugin(fetchInstance,
{ cacheKey, staleTime = 0,}) {
onBefore(params) {
const cachedData = _getCache(cacheKey, params);
if (!cachedData || !Object.hasOwnProperty.call(cachedData, 'data')) {
return {};
}
//如果staleTime=-1表示永不过期,或者当前时间减去缓存时间小于等过保质期的话,说明在保质期内
if (staleTime === -1 || new Date().getTime() - cachedData.time <= staleTime) {
return {
loading: false,
data: cachedData.data,
returnNow: true
}
} else {
return {
data: cachedData.data
}
}
},
参数缓存
缓存的数据包括 data
和 params
,通过 params
缓存机制,我们可以记忆上一次请求的条件,并在下次初始化。
举例:
const { data, params, loading, run } = useRequest(getArticle, {
cacheKey: 'cacheKey-demo',
});
// params 在fetch实例中以列表存储,所以需要params[0]获取。
const [keyword, setKeyword] = useState(params[0] || '');
- useRequestImplement.js
return {
loading: fetchInstance.state.loading,
。。。
params: fetchInstance.state.params || []
}
- useCachePlugin.js
// 初次挂载的时候 有没有缓存的参数。
useCreation(() => {
// 没有缓存key,返回
if (!cacheKey) return;
// 有缓存数据,给fetch实例绑定上 缓存里的参数和结果。
const cachedData = _getCache(cacheKey);
if (cachedData && Object.hasOwnProperty.call(cachedData, 'data')) {
fetchInstance.state.data = cachedData.data;
fetchInstance.state.params = cachedData.params;
// 不过期 loading 为 false
if (staleTime === -1 || new Date().getTime() - cachedData.time <= staleTime) {
fetchInstance.state.loading = false;
}
}
})
删除缓存
ahooks 提供了一个 clearCache
方法,可以清除指定 cacheKey
的缓存数据。
有两种key
的形式,字符串和数组。
- cache.js
const clearCache = (key) => {
if (key) {
// key=['key1','key2']
if (Array.isArray(key)) {
key.forEach(item => cache.delete(item));
} else {
//key='cacheKey'
cache.delete(key);
}
} else {
// 么有传key,就全清了
cache.clear();
}
}
export {
setCache,
getCache,
clearCache
}
- index.js
import { clearCache } from './src/utils/cache';
export { clearCache }
这样就能 通过 import { useRequest, clearCache} from 'ahooks';
获取到了
自定义缓存
通过配置 setCache
和 getCache
,可以自定义数据缓存,比如可以将数据存储到 localStorage
、IndexDB
等。
请注意:
setCache
和getCache
需要配套使用。- 在自定义缓存模式下,
cacheTime
和clearCache
不会生效,请根据实际情况自行实现。
举例:
const { data, loading } = useRequest(getArticle, {
cacheKey,
setCache: (data) => localStorage.setItem(cacheKey, JSON.stringify(data)),
getCache: () => JSON.parse(localStorage.getItem(cacheKey) || '{}'),
});
实现:
- useCachePlugin.js
function useCachePlugin(fetchInstance,
//重命名下
{ cacheKey, staleTime = 0, setCache: customSetCache, getCache: customGetCache }) {
const currentPromiseRef = useRef();
const unSubscribeRef = useRef();
// 判断有无 customSetCache,有就用,没有就用原本的
const _setCache = (key, cachedData) => {
if (customSetCache) {
customSetCache(cachedData);
} else {
cache.setCache(key, cachedData);
}
cacheSubscribe.trigger(key, cachedData.data);
}
const _getCache = (key) => {
if (customGetCache) {
return customGetCache(key);
} else {
return cache.getCache(key);
}
}
数据共享
同一个 cacheKey
的内容,在全局是共享的,这会带来以下几个特性
- 请求
Promise
共享,相同的cacheKey
同时只会有一个在发起请求,后发起的会共用同一个请求Promise
- 数据同步,任何时候,当我们改变其中某个
cacheKey
的内容时,其它相同cacheKey
的内容均会同步
实现:
要做到第一点特性,需要缓存promise
- cachePromise.js
const cachePromise = new Map();
const setCachePromise = (key, promise) => {
cachePromise.set(key, promise);
// 和 cachekey类似,请求结束 后 去掉缓存
promise.finally(() => {
cachePromise.delete(key);
});
}
const getCachePromise = (key) => {
return cachePromise.get(key);
}
export {
setCachePromise,
getCachePromise
}
- useCachePlugin.js
const currentPromiseRef = useRef();
onRequest(service, args) {
// 第二次进来,用缓存里的promise
let servicePromise = cachePromise.getCachePromise(cacheKey);
if (servicePromise && servicePromise !== currentPromiseRef.current) {
return {//缓存里有,和自己的不一样,用缓存中的
servicePromise
};
}
// 第一次进,获取promise,存起来。
servicePromise = service(...args);
currentPromiseRef.current = servicePromise;
cachePromise.setCachePromise(cacheKey, servicePromise);
return {
servicePromise
}
},
第二个特性,这里需要 订阅监听,才能做到同步。请求成功后,订阅该key
更新值的功能,当数据缓存时,去触发该key
下的监听,传入data
,让这个 key
下的data
都得到更新
- useCachePlugin.js
import * as cacheSubscribe from '../utils/cacheSubscribe';
const unSubscribeRef = useRef();
const _setCache = (key, cachedData) => {
...// 缓存 存的时候 触发 监听执行更新值的功能
cacheSubscribe.trigger(key, cachedData.data);
}
onSuccess(data, params) {
if (cacheKey) {
_setCache(cacheKey, {
data,//请求返回的数据
params,//请求的参数
time: new Date().getTime()//请求返回的时间
});
// 请求成功后 订阅 更新值的功能
unSubscribeRef.current = cacheSubscribe.subscribe(cacheKey, d => {
fetchInstance.setState({ data: d });
});
}
}
}
- cacheSubscribe.js
const listeners = {};
function subscribe(key, listener) {
if (!listeners[key]) {
listeners[key] = []
}
listeners[key].push(listener);
return function () {
const index = listeners[key].indexOf(listener);
listeners[key].splice(index, 1);
}
}
// 触发 d => {fetchInstance.setState({ data: d });
function trigger(key, data) {
if (listeners[key])
listeners[key].forEach(listener => listener(data));
}
export {
subscribe,
trigger
};