react离线缓存、图片缓存方案

React 做"离线缓存 + 图片缓存",建议不要把它当成一个单一缓存问题,而是拆成三层:

  1. API / JSON 数据缓存

  2. 图片资源缓存

  3. 整个页面 / App 的离线能力

一个比较成熟的方案是:

复制代码
React
 ├─ React Query / TanStack Query
 │    └─ API 数据缓存、失效、后台刷新
 │
 ├─ Service Worker
 │    ├─ API 离线兜底
 │    └─ 静态资源缓存
 │
 └─ 图片
      ├─ HTTP Cache
      ├─ Service Worker Cache
      └─ IndexedDB(只在特殊场景使用)

一、API 数据缓存:优先 TanStack Query

如果项目是 React,推荐直接使用 TanStack Query,而不是自己写 localStorage 缓存。

例如:

复制代码
const { data, isLoading } = useQuery({
  queryKey: ['products', categoryId],
  queryFn: () => fetchProducts(categoryId),
  staleTime: 5 * 60 * 1000,
  gcTime: 24 * 60 * 60 * 1000,
});

它解决的是:

  • 内存缓存

  • 请求去重

  • stale-while-revalidate

  • 后台刷新

  • 请求失败重试

  • cache invalidation

如果要求刷新页面后仍然有缓存,可以持久化 Query Cache:

复制代码
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'

const persister = createSyncStoragePersister({
  storage: window.localStorage,
})

不过,大量数据不建议放 localStorage,因为容量和同步 API 都比较弱。生产项目更推荐 IndexedDB。


二、图片缓存:优先 HTTP Cache

很多时候其实不需要自己实现图片缓存。

服务器正确配置:

复制代码
Cache-Control: public, max-age=31536000, immutable

然后图片 URL 使用版本化:

复制代码
/avatar/user-123-v3.webp

这样浏览器本身就是最有效的图片缓存层。

React:

复制代码
<img
  src="https://cdn.example.com/images/a8f3c.webp"
  loading="lazy"
  decoding="async"
/>

推荐:

复制代码
CDN
 ↓
HTTP Cache
 ↓
Browser Disk Cache

这比把所有图片主动塞进 IndexedDB 通常更简单。


三、需要"离线还能看到图片":Service Worker

这是离线图片缓存最值得采用的方案。

例如:

复制代码
第一次访问
    ↓
请求图片
    ↓
Service Worker
    ↓
网络成功
    ↓
写入 Cache Storage

以后:

复制代码
请求图片
   ↓
Service Worker
   ↓
Cache 命中 → 直接返回
   │
   └─ 未命中 → 请求网络 → 缓存 → 返回

简单示例:

复制代码
self.addEventListener('fetch', event => {
  const request = event.request

  if (request.destination === 'image') {
    event.respondWith(
      caches.match(request).then(cached => {
        if (cached) {
          return cached
        }

        return fetch(request).then(response => {
          const clone = response.clone()

          caches.open('images-v1').then(cache => {
            cache.put(request, clone)
          })

          return response
        })
      })
    )
  }
})

不过生产环境不要直接照这个写,还要考虑:

  • cache size

  • LRU 淘汰

  • CDN URL 参数

  • opaque response

  • 图片版本更新

  • 缓存过期

  • storage quota

  • 跨域


四、Cache Storage 和 IndexedDB 怎么选?

这是一个比较重要的区别:

场景 推荐
API JSON TanStack Query
API 离线数据 IndexedDB + TanStack Query Persist
CSS / JS / HTML Cache Storage
图片 HTTP Cache / Cache Storage
大量图片二进制 通常 Cache Storage
图片 Blob + 自定义 metadata IndexedDB
需要搜索、排序、复杂离线数据库 IndexedDB

不要默认把图片全部放 IndexedDB。

浏览器本身的 Cache Storage 对 Request / Response 设计得更自然。


五、如果你要做 PWA

比较完整的架构可以是:

复制代码
                    ┌───────────────┐
                    │    React UI   │
                    └───────┬───────┘
                            │
              ┌─────────────┴─────────────┐
              │                           │
       TanStack Query                Image <img>
              │                           │
       Query Cache                   HTTP Cache
              │                           │
        IndexedDB                 Service Worker
              │                           │
              └─────────────┬─────────────┘
                            │
                       Cache Storage
                            │
                         Network

六、推荐直接使用 Workbox

生产项目不太建议手撸完整 Service Worker,Workbox 会舒服很多。

例如 Vite:

复制代码
npm install vite-plugin-pwa

配置:

复制代码
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        runtimeCaching: [
          {
            urlPattern: /\.(?:png|jpg|jpeg|webp|svg)$/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'images',
              expiration: {
                maxEntries: 200,
                maxAgeSeconds: 60 * 60 * 24 * 30,
              },
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
        ],
      },
    }),
  ],
})

这个方案已经可以覆盖大多数 React Web App。


七、图片缓存策略怎么选?

主要有三个:

CacheFirst

复制代码
Cache → Network

适合:

  • 用户头像

  • 商品图片

  • Banner

  • 静态图片

优点是速度最快。


NetworkFirst

复制代码
Network → Cache

适合:

  • 经常变化的图片

  • 用户上传后的资源

  • 需要尽量保证最新版本的资源


StaleWhileRevalidate

复制代码
Cache → 立即返回
      ↓
后台请求最新图片
      ↓
更新 Cache

非常适合:

复制代码
新闻图片
Feed 图片
商品列表
首页推荐

用户体验通常很好。


八、不要用 localStorage 存图片

不建议这样:

复制代码
localStorage.setItem(
  'image',
  base64
)

原因:

  • 容量小

  • Base64 会膨胀

  • 同步 API

  • 主线程开销

  • 不适合大量二进制

图片缓存优先:

复制代码
HTTP Cache
    ↓
Service Worker / Cache Storage
    ↓
IndexedDB(特殊需求)

九、我比较推荐的实际技术栈

如果是一个正常的 React + Vite 项目:

复制代码
React
├── TanStack Query
├── IndexedDB
│   └── Dexie
├── vite-plugin-pwa
├── Workbox
└── CDN

对应关系:

复制代码
接口数据
→ TanStack Query

接口离线持久化
→ IndexedDB / Dexie

JS/CSS/HTML
→ Service Worker

图片
→ CDN + HTTP Cache
→ Workbox CacheFirst / StaleWhileRevalidate

整个 App 离线
→ PWA + Service Worker

如果你的应用是后台管理系统 / 电商 / 社交 Feed / 移动端 H5 中的某一种,我还可以进一步给你一套针对该场景的缓存架构 + React/Workbox 完整代码结构

相关推荐
SomeB1oody1 小时前
【RustyML入门】3.7. 循环层
开发语言·后端·机器学习·rust·教程
Canace1 小时前
笔记本都合上了,Claude 为什么还能在手机上执行电脑上装的技能?
前端·人工智能·ai编程
cyadyx2 小时前
Vite 比 Webpack 构建效率更高
前端·webpack·node.js·vite
mCell4 小时前
AI 时代 SVG 画图的潜力
前端·agent·svg
言乐64 小时前
Python游戏水平测试辅助系统
开发语言·python·游戏·django·pygame
我命由我123459 小时前
CesiumJS 笔记 - 获取容器中心点、Cartesian3 clone 方法、修改 Cartesian3 对象的高度
前端·javascript·css·前端框架·html·html5·js
Hopebearer_10 小时前
页面突然只剩 DOM?一次静态资源版本错配排查
前端·部署
抱抱宝10 小时前
Agent-study项目教程(03):手写 Mini-ReAct Agent(不依赖框架)
javascript·人工智能·gpt·react.js·prompt·agent
东风破_11 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范