0. 什么是占位模式缓存
缓存占位模式是将响应数据持久化,它将在刷新页面后立即更新到 data state 中作为占位数据,同时发送请求,开发者可以在响应前使用占位数据替代 Loading 状态。
简单的说,占位模式允许我们先用本地的数据先进行展示,同时发送请求,在接口返回数据后在对数据进行更新。占位模式从一定程度上避免了用户对慢接口的等待焦虑。
在之前的文章还在用axios发送请求?来看看alova的高阶用法!介绍过缓存占位模式,但是那个针对alova2的版本,在最新的alova3版本中,作者移除了这种缓存模式。
1. 使用initialData实现
在alova2-3的迁移文档中,作者推荐使用initialData实现占位模式的效果。
// v2
const { data } = useRequest(Getter, {
placeholder: true
});
// v3
const { data } = useRequest(Getter, {
initialData() {
// 设置上一次的响应数据
const storedData = localStorage.getItem('placeholder-data');
return JSON.parse(storedData || '{}');
// 也使用alova的存储适配器
// return alovaInst.l2cache.get('placeholder-data');
}
}).onSuccess(({ data, method }) => {
// 保存响应数据
localStorage.setItem('placeholder-data', JSON.stringify(data));
// 也使用alova的存储适配器
alovaInst.l2cache.set('placeholder-data', data);
});
这种方式虽然能实现,但相对上比较简陋:
- 无法复用,每次都需要重复设置
initialData和onSuccess - 这里
localStorage的key是静态的,无法根据请求参数动态进行设置
2.使用middleware实现
基于以上这些问题,我们对alova3的useRequest进行二次封装,基于useRequest的中间件实现一个hook来完成占位模式的缓存功能:
2-1.代码实现
import { useRequest } from 'alova/client';
import { type Method } from 'alova';
const getCacheKey = (m: Method) => `PH_${m.key}`
// 占位模式缓存请求
function usePHRequest(...args: Parameters<typeof useRequest>): ReturnType<typeof useRequest> {
// 解析useRequest的2个入参
const [method, config = {}] = args
// 原始中间件函数
const originalMiddleware = config.middleware
config.middleware = (_, next) => {
// 执行原始中间件
if (originalMiddleware) {
originalMiddleware(_, next)
}
// 查询本地缓存
const cacheData = localStorage.getItem(getCacheKey(_.method))
next().then(data => {
localStorage.setItem(getCacheKey(_.method), JSON.stringify(data))
// 更新数据
_.proxyStates.data.v = data
_.controlLoading(false)
})
if (cacheData) {
_.controlLoading(false)
return JSON.parse(cacheData)
}
}
return useRequest(method, config)
}
export default usePHRequest;
2-2.代码解析
- 定义了一个hook
usePHRequest,注意它的出参与入参跟useRequest完全一致,在使用上没有任何负担 - 解析了
useRequest的两个入参method与config,这里的config可能为空,同时单独保存原始的中间件函数 - 重写了中间件,首先执行了原始的中间件函数,避免丢失
- 查询本地缓存
- 如果有缓存,就直接返回本地缓存,同时继续发送请求,请求回来后再次更新本地缓存
- 在获取到数据后,主动将loading状态设置为
false
2-3.为啥不
- 为啥不直接用
initialData
在initialData中没法获取运行时的method对象,也就获取准确的请求参数,无法生成一个唯一的缓存key
- 为啥不在
onSuccess中处理响应
因为我们完全继承了useRequest的入参,它本身也会返回一个onSuccess回调,为了避免与此冲突,我们直接使用了promise.then进行处理。这也是为什么保存了原始中间件函数然后收到进行执行的原因。
3.实战使用
3-1.接口定义
首先通过APIFOX上mock实现一个慢查询的接口,这个接口我们设置了至少需要3s的时间。
然后再通过alova定义接口:
const alova = createAlova({
baseURL: 'https://apifoxmock.com/m1/4992867-4651676-default', // APIFOX mock接口
statesHook: VueHook,
requestAdapter: adapterFetch(),
timeout: 20000,
responded: {
// 请求成功的拦截器
onSuccess: async (response: Response) => {
return response.json()
}
}
})
interface getSlowListRes {
list: Pet[]
}
// 慢查询接口
export const getSlowList = () => {
return alova.Get('/pet/slow-list', {
transform: (res: getSlowListRes) => res.list,
cacheFor: null
})
}
3-2.使用usePHRequest
import usePHRequest from '@/hooks/usePHRequest'
import * as API from '@/API'
import { h } from 'vue'
import { type Pet } from '@/API/model.d'
import { NImage, NTag, NSpace } from 'naive-ui';
const { data: petList, loading, send } = usePHRequest(API.getSlowList, {
middleware: (_, next) => {
console.log('custom middleware')
return next()
}
}).onSuccess(() => {
console.log('on success')
})
可以看到,在使用上非常的简单,跟useRequest保持一致。
这里我们额外配置middleware和onSuccess:
在运行时,可以发现middleware和onSuccess都已经成功执行,说明我们刚才的实现方式对这两个配置并没有任何的阻碍。
3-3.运行效果
在刷新页面后,可以看到表格先展示了本地的数据,然后同时异步请求接口,在获取到数据后,更新返回数据同时更新了本地的占位缓存数据:
希望以上分享对你有所帮助,完整代码已经上传到Gitee上,欢迎自取,