Alova3.0占位模式缓存的实现方式

428 阅读4分钟

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);
});

这种方式虽然能实现,但相对上比较简陋:

  • 无法复用,每次都需要重复设置initialDataonSuccess
  • 这里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.代码解析

  • 定义了一个hookusePHRequest,注意它的出参与入参跟useRequest完全一致,在使用上没有任何负担
  • 解析了useRequest的两个入参method与config,这里的config可能为空,同时单独保存原始的中间件函数
  • 重写了中间件,首先执行了原始的中间件函数,避免丢失
  • 查询本地缓存
  • 如果有缓存,就直接返回本地缓存,同时继续发送请求,请求回来后再次更新本地缓存
  • 在获取到数据后,主动将loading状态设置为false

2-3.为啥不

  1. 为啥不直接用initialData

initialData中没法获取运行时的method对象,也就获取准确的请求参数,无法生成一个唯一的缓存key

  1. 为啥不在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:

image.png

在运行时,可以发现middleware和onSuccess都已经成功执行,说明我们刚才的实现方式对这两个配置并没有任何的阻碍。

3-3.运行效果

Nov-18-2024 17-07-10.gif

在刷新页面后,可以看到表格先展示了本地的数据,然后同时异步请求接口,在获取到数据后,更新返回数据同时更新了本地的占位缓存数据:

image.png


希望以上分享对你有所帮助,完整代码已经上传到Gitee上,欢迎自取,