
字节笔记本
2026年10月7日 · 约 10 分钟读完
读可以缓存,写不要进缓存
同一条 GET 被很多组件反复打时,可以在过期之前复用上一次的响应。对话里给了三条路:axios 上套 axios-cache-adapter,Vue 里用 vue-request 的 cacheKey,或者自己用 Map 按时间判断。写操作和会变的数不要进这三份缓存。

适配器只包在 axios 实例上
setupCache 的样本把 maxAge 设为 15 * 60 * 1000,十五分钟。exclude.query 设为 false,表示带查询串的请求也可以缓存。然后 axios.create({ adapter: cache.adapter })。组件里 api.get('/api/data') 走这个实例,不走默认的 axios。
带分页的例子是 api.get('/products', { params: { page: 1, size: 10, category: 'electronics' } })。page 不同就是不同的请求。查询参与缓存键时,第一页不会把第二页的结果吐出来。若实现把查询丢掉,分页会串。样本允许查询进入缓存,就是为了这个。
进一步的排除写在另一份配置里:methods 排除 post、put、delete。paths 排除含 security、payment、realtime 的路径。filter 在 req.params?.noCache === true 时不缓存。地区和分类这种公共 GET,例如 /common/regions、/common/categories,可以进缓存。股价 /stock/price、订单状态 /orders/status 不要进。安全设置和支付详情即使是 GET 也不要进,路径已经写在排除列表里。
store2 在样本里是可选的本地存储。默认不写的话,缓存跟这一个 axios 实例的内存走,刷新页面就没了。要跨刷新保留,才接存储。敏感接口即使接了存储也不该写入。
vue-request 用键和两段时间
useRequest('/api/data', { cacheKey: 'uniqueKey', cacheTime: 5 * 60 * 1000, staleTime: 0 }) 返回 data、loading、error。cacheKey 要能区分不同参数。两个组件共用一个键,就会共用一份数据。列表若把键写成固定的 uniqueKey,筛选条件变了仍会看到上一次的列表。键里带上分页和分类。
cacheTime 五分钟是样本。staleTime: 0 表示数据一旦返回就可以视为过期,下次仍可能重新请求,但缓存窗口内还能先拿出旧值。这两个数不要抄反。把 cacheTime 设成 0,缓存等于关掉。
已经在用 axios、只想少发 GET,用适配器。想要 loading 和 error 进模板,用 useRequest。两套一起包在同一次 GET 上,会有两层过期时间,排错时分不清是哪一层命中。
Map 要自己看时间戳
第三份是 cacheRequest(key, request, options)。默认 maxAge 五分钟。cache.get(key) 之后,若 Date.now() - cached.timestamp < maxAge 就返回 cached.data。否则 await request(),再 cache.set 带上新的时间戳。
const cached = cache.get(key)
if (cached && Date.now() - cached.timestamp < maxAge) {
return cached.data
}这个 Map 是模块级的一份。键如果只用 'api/data',所有参数共用一条。调用样本正是这个固定键,加上 { maxAge: 10 * 60 * 1000 }。接分页时把页码写进键。
Map 不会在过期时删条目,只是读取时比较时间。过期条目还占着内存,下次同一个键会覆盖。没有上限。条目种类很多时,这是一份会一直长大的表。适配器那条至少有 maxAge 由库管。自己写的要有个数上限,或者只缓存那几个公共字典。
并发时两个调用都看见未命中,会打出两次请求,后返回的覆盖先返回的。样本没有把进行中的 Promise 放进 Map。同一个键在飞行中时,应该把那一个 Promise 存下来给后来的调用,而不是各自 request()。

用两个请求把命中和排除分开验
先打一次 GET /common/regions,再立刻打同一地址。第二次不应再出现新的网络往返,前提是还在 maxAge 里。适配器样本是十五分钟,vue-request 的 cacheTime 是五分钟,手写 Map 的调用样本是十分钟。三套数字不要混在同一次验收里。验收哪一套,就只启用那一套。
再打 GET /products,参数分别是 page=1 和 page=2,size 都是 10,category 都是 electronics。两次结果必须不同。若第二页拿到第一页的数据,查询没有进键。适配器里 exclude.query: false 就是允许查询参与缓存。手写 Map 的样本键是固定的 'api/data',照抄会把所有页合成一条,所以键要改成包含 page 和 category。
然后发 POST /orders。methods 排除了 post、put、delete,这次必须真的打到服务器。GET /stock/price 和 GET /orders/status 也不缓存。GET 路径里带 security 或 payment 的,被 paths 排除。再发一个带 params.noCache === true 的 GET,filter 应让它跳过缓存。这四类失败的表现一样:每次都是新请求。不要用“第二次变快了”来判断它们,变快可能只是服务器自己的缓存。
useRequest 的 cacheKey 若写成常量 uniqueKey,两个不同地址会共用数据。键里放上路径和参数。staleTime: 0 时,组件仍可能先看到缓存里的值再刷新。界面闪一下旧数据,不一定是键写错,要看 cacheTime 是否还没到。
手写函数在未命中时没有锁。用两个同时发出的调用看网络面板,若出现两条相同请求,就是样本的缺口。把进行中的 Promise 按键存进 Map,后来的调用等待同一个 Promise,完成后再写入带时间戳的数据。过期判断仍用 Date.now() - timestamp < maxAge。过期后不删除旧键也可以,下一次会覆盖。没有容量上限时,只缓存地区、分类这种少量字典,不要把每一次搜索都放进去。
store2 是可选的。接上之后缓存会留在本地存储里,刷新页面还在。支付和安全接口不要走这条。内存里的 Map 刷新就没了,对公共字典往往够用。
键里要有参数,写请求不进缓存
GET 公共数据可以缓存。axios-cache-adapter 用十五分钟的 maxAge,并排除 POST、PUT、DELETE 以及安全、支付、实时路径。vue-request 的 cacheKey 必须带上筛选条件。手写 Map 用时间戳比较,键要包含参数,飞行中的请求不要各打一遍。股价、订单状态、支付详情不缓存。两套库不要包同一次调用。
验收分开做。公共 GET /common/regions 在十五分钟、五分钟或十分钟的窗口里应命中对应那一套缓存,三套不要同时开。/products 的 page 从 1 换成 2,size 10,category 为 electronics,结果必须不同。POST /orders、PUT 和 DELETE 每次都打到服务器。/stock/price、/orders/status、带 security 或 payment 的路径,以及 noCache 为真的请求,也不进缓存。cacheKey 里要有路径和参数,不能是固定的 uniqueKey。手写 Map 用 Date.now() 减 timestamp 和 maxAge 比较。同一个键同时来两次时,应共用一个正在进行的 Promise。store2 可选,接上之后不要把支付和安全响应写进去。刷新页面后内存 Map 会空,这是预期,不是失效。
三套过期时间不要放在同一次试验里。适配器那套大约十五分钟,组合式请求那套大约五分钟,手写映射那次调用大约十分钟。试验哪一套就只打开哪一套。列表请求要把页码、每页条数和分类放进键里,否则第二页会拿到第一页。写入、更新和删除每次都要到达服务器。股价和订单状态即使是读取也不缓存。路径里出现安全、支付或实时这几个意思时排除。带上明确不缓存的参数时,过滤函数应放过这次请求。同一个键若已经有一个尚未完成的请求,后来的调用等待它,而不是再发一次。可选的本地存储不要收支付和安全的响应。页面刷新后,只放在内存里的映射变空是正常的。两套库不要叠在同一次读取上。命中与否以网络面板里有没有第二次请求为准。公共字典可以进缓存,个人支付不可以。



