如何用 IndexedDB 存储用户的草稿箱内容防止刷新导致的数据丢失

作者:袖梨 2026-08-07

处理如何用 IndexedDB 存储用户的草稿箱内容防止刷新导致的数据丢失这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。

IndexedDB存草稿需先确保数据库就绪,用getDB()封装open()并每次调用;主键用组合ID或crypto.randomUUID()避免覆盖;节流写入、比对内容防无效提交;卸载前同步保存并兜底sessionStorage;多标签页取最新草稿。

草稿存 IndexedDB 之前先检查数据库是否就绪

IndexedDB 不是直接可用的,得等 indexedDB.open() 成功回调后才能写入。如果页面一加载就急着调 put(),大概率会报 InvalidStateError: Failed to execute 'put' on 'ObjectStore': The transaction has finished.——因为事务已关闭或压根没开。

实操建议:

  1. 封装一个 getDB() 工具函数,内部用 Promise 包裹 indexedDB.open(),只 resolve 成功的 db 实例,拒绝掉所有升级/打开失败
  2. 每次存草稿前都调一次 getDB(),不复用旧 db 引用(它可能已 close)
  3. 别在 upgradeneeded 里建好 store 就以为万事大吉——versionchange 事务自动结束,后续写入必须新开事务

草稿数据结构设计要带时间戳和唯一标识

用户可能同时编辑多个表单(比如两封邮件、三个笔记),只用一个 draft 键存值会互相覆盖。而且没时间戳,就无法判断哪份更新、是否该自动恢复。

实操建议:

  1. 主键用组合 ID:`${formType}-${userId}-${timestamp}` 或直接用 crypto.randomUUID()(注意 IE 不支持,需 fallback)
  2. 值对象至少包含:{ content: string, updatedAt: number, formId: string, isAutoSaved: boolean }
  3. store 设计为 keyPath: 'id',启用 autoIncrement: false,避免索引混乱

监听输入并节流写入,别每敲一个字都触发 put

高频输入下频繁开事务 + put() 会卡 UI,还可能因事务冲突失败(尤其多标签页编辑同一草稿时)。浏览器对 IndexedDB 的并发事务控制很严格。

实操建议:

  1. setTimeout 实现简单节流:输入停止 1.5 秒后再存,期间新输入则 clearTimeout 重置
  2. 写入前先用 get() 拿旧值比对 contentupdatedAt,内容未变就不提交(防无意义写入)
  3. 事务设为 readwrite,但只在一个事务里做 get + put,别混其他操作

页面卸载前强制保存一次,但得防 Safari 的 beforeunload 限制

beforeunload 是最后防线,但 Safari 对它的执行有严格限制:不能异步等待 IndexedDB,否则直接丢弃事务。Chrome 也倾向快速返回,不等 Promise。

实操建议:

  1. 卸载前不走 Promise 链,直接同步调 getDB().then(db => { ... }) 内部用 db.transaction(...).objectStore(...).put(...)
  2. 加个兜底:把当前草稿先存到 sessionStorage,哪怕 IndexedDB 失败,刷新后也能从 sessionStorage 恢复(再尝试写入 DB)
  3. 别依赖 visibilitychange 事件——切到其他 tab 不一定触发,且 iOS Safari 常静默丢弃后台页

最易被忽略的是多标签页场景:两个窗口同时编辑同一草稿,后保存的会覆盖先保存的。真要解决得加乐观锁(比如用 updatedAt 时间戳校验),但多数产品只需“最后保存生效”,那就得在恢复草稿时优先取时间最新的那条记录——别只查 get(id),要用 openCursor() 配合 IDBKeyRange.bound 扫一遍同 formId 的所有草稿。

相关文章

精彩推荐