在一些不常见的项目配置信息里,我们如果不缓存到内存里的话,可能到一个页面就需要重新请求,造成api请求次数的消耗。对于这种变化小,使用地方多的,我们可以使用内存缓存处理,第一次从api取,后续从内存里取。
如果数据变化了,需要及时清理内存重新获取。
也可以使用 pinia 定义 store,这种方式我才写完,leader说这里先不处理,我只好找个地方存储一下这个方法。

/**
 * 内存缓存 刷新页面即重新加载
 * 支持防止重复请求、缓存过期、响应式数据等功能
 */
import { type Ref, ref, shallowRef } from 'vue'
import type { ApiError } from '@/models'

interface CacheOptions {
  /** 缓存过期时间(毫秒),默认不过期 */
  ttl?: number
  /** 是否使用浅响应 */
  shallow?: boolean
}

interface CacheEntry<T> {
  data: T
  timestamp: number
}

export default function useMemoryCache<K = string, V = any>(options: CacheOptions = {}) {
  const { ttl, shallow = false } = options

  const cache = new Map<K, CacheEntry<V>>()
  const loadingMap = new Map<K, Promise<V>>()
  const errorMap = new Map<K, ApiError | undefined>()

  function has(key: K): boolean {
    if (!cache.has(key)) { return false }

    // 检查是否过期
    if (ttl) {
      const entry = cache.get(key)!
      const now = Date.now()
      if (now - entry.timestamp > ttl) {
        cache.delete(key)
        return false
      }
    }

    return true
  }

  function get(key: K): V | undefined {
    if (!has(key)) { return undefined }
    return cache.get(key)!.data
  }

  function set(key: K, value: V): void {
    cache.set(key, {
      data: value,
      timestamp: Date.now(),
    })
  }

  function remove(key: K): void {
    cache.delete(key)
    loadingMap.delete(key)
  }

  function clear(): void {
    cache.clear()
    loadingMap.clear()
  }

  /**
   * 获取或加载数据(防止重复请求)
   */
  async function init(
    key: K,
    loader: () => Promise<V>,
  ): Promise<V> {
    // 返回缓存
    if (has(key)) {
      return get(key)!
    }

    // 复用正在进行的请求
    if (loadingMap.has(key)) {
      return loadingMap.get(key)!
    }

    // 发起新请求
    errorMap.delete(key)
    const promise = loader()
      .then((data) => {
        set(key, data)
        return data
      }).catch((err: unknown) => {
        errorMap.set(key, err as ApiError)
        throw err
      }).finally(() => {
        loadingMap.delete(key)
      })

    loadingMap.set(key, promise)
    return promise
  }

  async function reload(key: K, loader: () => Promise<V>): Promise<V> {
    remove(key)
    return init(key, loader)
  }

  /**
   * 创建响应式数据(配合 getOrLoad 使用)
   */
  function createReactive(key: K, defaultValue: V): Ref<V> {
    const state = shallow ? shallowRef(defaultValue) : ref(defaultValue)

    if (has(key)) {
      state.value = get(key)!
    }

    return state as Ref<V>
  }

  function getError(key: K): ApiError | undefined {
    return errorMap.get(key)
  }

  function getLoading(key: K): boolean {
    return loadingMap.has(key)
  }

  return {
    has,
    get,
    set,
    remove,
    clear,
    init,
    reload,
    createReactive,
    getError,
    getLoading,
  }
}