uni-app 数据缓存(3 种思路对比),含完整示例与避坑

猿友 2026-09-23 13:04:10 浏览数 (53)
反馈

持久化小数据用 uni.setStorage,跨端同步或复杂状态用 Vuex/Pinia,需要服务端共享用 Redis;按数据生命周期选。uni-app 数据缓存是每一个跨端项目都会遇到的命题:用户登录态、主题偏好、表单草稿、接口缓存,这些数据该放哪里、能活多久、会不会丢,直接决定体验与稳定性。很多初学者把所有东西都写进本地存储,结果碰到多端差异、容量上限、读取旧值等问题才回头补课。本文用三种思路的横向对比,帮你在项目起步时就做对选型,并给出可复制的完整示例与常见坑位。我们会从最小的 uni.setStorage 写起,再到 Vuex/Pinia 的跨页共享,最后落到服务端的 Redis 方案与排错清单。

uni-app 数据缓存封面图

一、先看结论:uni-app 数据缓存怎么选

很多团队在选型时纠结,本质是没有先回答一个问题:这份数据是为谁服务、活到什么时候、丢了对不对体验造成致命影响。把生命周期想清楚,选型就水到渠成。

下面这张表把三种主流思路放在一张平面上对比,建议截图保存,作为团队评审时的统一口径。

方式 适用场景 不推荐场景 推荐顺序
uni.setStorage 本地存储 登录态、用户偏好、表单草稿、接口缓存等小数据,需要重启 App 后仍保留 跨端实时同步、多设备共享、体积超过 10MB 的内容、敏感密钥 ① 首选(小数据持久化)
Vuex / Pinia 状态管理 跨页面共享的瞬时状态、购物车、登录用户信息,强调响应式与可调试 需要跨设备长期保存、需要服务端鉴权校验的数据 ② 次之(内存态共享)
服务端缓存 Redis 多端共享的会话、热点配置、限流计数,需要服务端统一权威来源 纯前端离线可用的小数据、对延迟零容忍的本地交互 ③ 按需(服务端共享)

结论很朴素:能在本地用 uni.setStorage 解决的,绝不引入 Redis;需要跨页面即时联动的,用 Vuex 或 Pinia 比反复读存储更优雅;只有真正需要多设备、多用户共享的数据,才值得把复杂度上升到服务端缓存。关于 setStorage 的官方参数约定,可以参考 uni-app 中文文档 中的存储 API 说明。

二、环境与最小示例

演示环境以 HBuilderX 创建的默认 uni-app 项目为准,运行基座可以是微信小程序、H5 或 App,三者对存储接口的调用方式完全一致,差异我们放到后面的排错章节讲。下面是最简的写入示例,刻意去掉所有业务包裹,只保留最核心的三行。

// 最简单的持久化写入:把用户昵称存到本地
uni.setStorage({
  key: \\\'nickname\\\',   // 存储键名,建议用有意义的字符串,避免与系统键或第三方 SDK 键冲突
  data: \\\'编程狮同学\\\', // 要存储的值,支持字符串、对象、数组等可序列化数据
  success() {        // 成功回调,表示已写入(该接口本身是异步的)
    console.log(\\\'已保存\\\');
  }
});

这段代码的含义是:以 nickname 为键,把字符串 编程狮同学 写入当前运行环境提供的本地存储区。success 回调在写入完成后触发,适合用来提示用户或继续执行依赖该数据的逻辑。它没有返回值,结果通过回调传递,这是 uni-app 早期 API 的统一风格。

三、思路一:uni.setStorage 本地存储

uni.setStorage 是 uni-app 数据缓存的第一道入口,它是对各端原生存储能力的统一封装:在 H5 端底层走 localStorage,在微信小程序端底层走小程序本地存储,在 App 端走系统文件或原生存储。正因为统一封装,开发者写一套代码就能覆盖多端,但它也把各端的差异掩盖了起来,这正是后面排错的重点。

该接口分为异步与同步两套:

  • 异步接口 uni.setStorage / uni.getStorage / uni.removeStorage 不阻塞主线程,适合在非启动路径上使用;
  • 同步接口 uni.setStorageSync / uni.getStorageSync / uni.removeStorageSync 立即返回结果,但会阻塞当前线程,仅在启动初始化等必须同步拿值的场景谨慎使用。

// 异步写入(推荐,不阻塞渲染与交互)
uni.setStorage({
  key: \\\'token\\\',                       // 键名,建议全局约定前缀,如 u_ 开头
  data: res.token,                    // 值,登录接口返回后写入
  success: () => console.log(\\\'token 已缓存\\\')
});

// 同步写入(会阻塞,谨慎在启动阶段使用)
try {
  uni.setStorageSync(\\\'token\\\', res.token); // 同步接口,失败会抛异常,必须包 try
} catch (e) {
  console.error(\\\'写入失败\\\', e);            // 捕获容量超限、序列化错误等异常
}

// 异步读取
uni.getStorage({
  key: \\\'token\\\',
  success: (r) => { /* r.data 即缓存值,未命中时 data 为空 */ }
});

// 同步读取
const token = uni.getStorageSync(\\\'token\\\'); // 直接返回缓存值,未命中返回空字符串

// 移除与清空
uni.removeStorageSync(\\\'token\\\'); // 删除单个键,登出时务必调用
uni.clearStorageSync();         // 清空全部(极度慎用,会丢掉所有本地数据)

关于生命周期:本地存储是持久化的,只要用户不主动清除、不卸载应用(部分端卸载会清掉),数据就会一直存在。但要注意,H5 端浏览器有隐私模式与配额限制,小程序端有 10MB 上限,App 端各平台策略不一。因此 uni.setStorage 适合放\\"丢了能重新获取\\"的小数据,绝不适合放大体积文件或作为唯一的数据真相来源。

四、思路二:Vuex / Pinia 状态管理

当一份数据需要在多个页面之间实时共享,并且希望改动后界面自动刷新,再反复调用 uni.getStorageSync 就显得笨拙且容易读到旧值。这时候应该用状态管理库在内存里维护一份单一数据源。Vue2 时代主流是 Vuex,Vue3 项目更推荐 Pinia,二者理念一致:全局 state + 可追踪的变更。

// Pinia 示例:定义一个全局用户 store,实现跨页共享登录态
import { defineStore } from \\\'pinia\\\';

// 创建一个名为 user 的 store,名字全局唯一,便于调试时定位
export const useUserStore = defineStore(\\\'user\\\', {
  state: () => ({
    token: \\\'\\\',      // 登录凭证,属于内存态,页面刷新或重进即丢失
    profile: null   // 用户资料对象,任意页面读取都拿到同一份
  }),
  actions: {
    // 登录成功后写入,任何页面通过 store.token 都能实时读到
    setToken(t) {
      this.token = t; // 直接赋值即触发响应式更新,无需手动通知
    }
  }
});

需要强调的是,内存态和本地存储是互补关系而非替代关系。典型做法是:启动时从 uni.setStorage 把 token 读进 Pinia,运行中靠 Pinia 做跨端状态共享与响应式更新,登出或定时再把关键字段落回本地。这样既享受了响应式的便利,又保住了持久化的安全垫。跨端状态设计的细节,建议在动手前先想清楚哪些字段必须持久、哪些只活在会话里。

五、思路三:服务端缓存 Redis 与排错

当数据需要在多设备、多用户之间保持一致,或者需要服务端做统一鉴权、限流、热点配置下发时,本地存储就无能为力了。此时应把权威数据放在服务端,用 Redis 做高速缓存,客户端只持有短时 token 或会话标识。Redis 的价值在于内存级读写与过期策略,能把数据库压力挡在前面。

# (以下为说明性代码,未在本机执行;预期结果:返回缓存命中的 JSON 字符串)
# 连接 Redis 并写入用户会话,键带业务前缀便于批量清理
SET session:uid:1001 \\\'{\\\"token\\\":\\\"abc\\\",\\\"exp\\\":1730000000\\\"}\\\' EX 3600
# EX 3600 表示 1 小时后自动过期,避免脏数据长期驻留造成串号
GET session:uid:1001
# 读取命中则返回上面的 JSON;未命中返回 nil,此时应回源数据库

下面这张排错表,整理了 uni-app 数据缓存实践中最高频的四类故障,按\\"现象—原因—修复方向\\"给出定位路径:

现象 常见原因 修复方向
读取到的永远是旧值或空值 异步写入未 await/未进 success 就立刻读取,时序错乱 把读取放到写入的回调或 Promise 之后,保证先写后读
iOS 或小程序报存储失败 storage 大小超限(小程序约 10MB),或写入不可序列化对象 拆分大对象、移除二进制,改用文件或服务端存储
Android 与 H5 行为不一致 各端底层存储实现不同,H5 受浏览器隐私模式影响 统一封装读写层,对异常做兜底,不依赖端特性
存对象后取出字段丢失 JSON 序列化失败,或含函数、循环引用、Date 等不可序列化值 先用 JSON.parse(JSON.stringify(x)) 验证,只存纯数据

需要服务端共享方案时,建议先系统学习 Redis 教程 中的数据结构与过期命令,再决定是否引入。本地与服务端缓存不是二选一,而是分层:本地兜底离线体验,服务端保证多端一致。

uni-app 数据缓存决策图

总结

回到开头那句结论:uni-app 数据缓存的选型,本质是看数据的生命周期与归属。持久化的小数据交给 uni.setStorage,它底层在 H5 走 localStorage、在微信小程序走小程序本地存储,是性价比最高的第一选择;跨页面、强联动的数据交给 Vuex/Pinia 做跨端状态共享,配合本地存储做持久化兜底;只有多设备、多用户共享的需求,才值得上 Redis 这类服务端缓存。三者不是互斥关系,而是从端到云的分层协作。把这套思路落地到项目里,你就能少踩\\"读到旧值\\"\\"容量超限\\"\\"跨端不一致\\"这些坑,让 uni-app 数据缓存真正稳定可靠。

延伸学习

常见问题

Q:uni.setStorage 和 localStorage 是一回事吗?

不完全一样。在 H5 端,uni.setStorage 底层确实调用了浏览器的 localStorage;但在小程序与 App 端,它映射的是各自的原生存储能力。所以它是统一封装,而不是 localStorage 的别名。好处是一套代码多端通用,代价是你不能直接假设它和浏览器 localStorage 在所有端表现一致,尤其是容量与清理策略。

Q:为什么我刚 setStorage 完,马上 getStorage 却读到旧值?

这是最典型的异步时序问题。uni.setStorage 是异步接口,如果你没在 success 回调或 Promise 完成后就去读取,读操作可能跑在了写入之前。修复办法是把读取逻辑放进写入的回调,或改用返回 Promise 的写法并 await。另外,若混用了同步与异步接口,也容易在同一时刻看到两个版本,建议项目内统一只用其中一种风格。

Q:登录 token 到底该存本地还是存状态管理?

推荐组合方案:用 uni.setStorage 做长期落地,用 Pinia/Vuex 做运行期共享。启动时把 token 从本地读进 store,运行中界面只依赖 store 的响应式数据,登出时同时清掉本地与 store。这样既能在重进 App 后自动登录,又避免了每个页面反复同步读存储带来的旧值风险。

0 人点赞