使用 IndexedDB 持久化应用状态的最佳实践

了解在 IndexedDB 和流行的状态管理库之间同步应用状态的最佳实践。

当用户首次加载网站或应用程序时,通常需要进行大量工作来构建用于呈现 UI 的初始应用程序状态。例如,有时应用需要在客户端验证用户身份,然后发出多个 API 请求,才能获得页面上显示的所有数据。

将应用程序状态存储在 IndexedDB 中是加快重复访问加载速度的好方法。然后,应用可以在后台与任何 API 服务同步,并使用 stale-while-revalidate 策略延迟更新 UI 以显示新数据。

但是,当使用 IndexedDB 时,有很多重要事项需要考虑,这些事项对于不熟悉 API 的开发者来说可能并不明显。本文档解答了常见问题,并讨论了在 IndexedDB 中持久化应用程序状态时需要牢记的一些最重要事项。

保持应用的可预测性

围绕 IndexedDB 的许多复杂性源于这样一个事实,即您(开发者)无法控制的因素太多。本节探讨了使用 IndexedDB 时必须牢记的许多问题。

存储写入可能失败

写入 IndexedDB 时可能会因各种原因发生错误,在某些情况下,这些原因超出了您作为开发者的控制范围。例如,某些浏览器不允许在 私密浏览模式下写入 IndexedDB。还有一种可能性是用户使用的设备几乎没有磁盘空间,浏览器会限制您存储任何内容。

因此,至关重要的是,您始终在 IndexedDB 代码中实现适当的错误处理。这也意味着通常最好将应用程序状态保存在内存中(除了存储之外),这样即使在私密浏览模式下运行或存储空间不可用时,UI 也不会崩溃(即使某些需要存储的其他应用功能无法工作)。

存储的数据可能已被用户修改或删除

与您可以限制未经授权访问的服务器端数据库不同,客户端数据库可供浏览器扩展程序和开发者工具访问,并且可以由用户清除。

虽然用户修改本地存储的数据可能并不常见,但用户清除数据却非常常见。重要的是,您的应用程序可以处理这两种情况而不会出错。

存储的数据可能已过期

与上一节类似,即使用户没有自行修改数据,他们存储的数据也可能是由早期版本的代码写入的,可能是包含 bug 的版本。

IndexedDB 内置了对模式版本和升级的支持,使用其 IDBOpenDBRequest.onupgradeneeded() 方法;但是,您仍然需要编写升级代码,使其能够处理用户从以前版本(包括包含 bug 的版本)升级的情况。

单元测试在这里非常有用,因为手动测试所有可能的升级路径和案例通常是不可行的。

保持应用的性能

IndexedDB 的关键功能之一是其异步 API,但不要因此而认为在使用它时无需担心性能。在许多情况下,不当使用仍然会阻塞主线程,从而导致无响应。

作为一般规则,IndexedDB 的读取和写入不应大于访问数据所需的量。

虽然 IndexedDB 可以将大型嵌套对象存储为单个记录(并且从开发者的角度来看,这样做确实非常方便),但应避免这种做法。原因是当 IndexedDB 存储对象时,它必须首先创建该对象的结构化克隆,并且结构化克隆过程发生在主线程上。对象越大,阻塞时间就越长。

当计划如何将应用程序状态持久化到 IndexedDB 时,这会带来一些挑战,因为大多数流行的状态管理库(如 Redux)的工作方式是将您的整个状态树作为一个单独的 JavaScript 对象进行管理。

虽然以这种方式管理状态有很多好处,并且虽然将整个状态树作为单个记录存储在 IndexedDB 中可能很诱人且方便,但在每次更改后都这样做(即使经过节流/防抖)也会导致不必要的主线程阻塞,它会增加写入错误的 likelihood,并且在某些情况下,它甚至会导致浏览器选项卡崩溃或变得无响应。

您应该将整个状态树分解为单独的记录,而不是将其存储在单个记录中,并且仅更新实际更改的记录。

与大多数最佳实践一样,这不是一个全有或全无的规则。在无法分解状态对象并仅写入最小更改集的情况下,将数据分解为子树并仅写入这些子树仍然优于始终写入整个状态树。小的改进胜于没有改进。

最后,您应该始终衡量您编写的代码的性能影响。虽然对 IndexedDB 的小写入确实比大写入执行得更好,但这只有在您的应用程序正在执行的 IndexedDB 写入实际上导致长时间任务阻塞主线程并降低用户体验时才重要。重要的是进行衡量,以便您了解您正在为优化什么。

结论

开发者可以使用 IndexedDB 等客户端存储机制来改善应用程序的用户体验,不仅可以跨会话持久化状态,还可以缩短重复访问时加载初始状态所需的时间。

虽然正确使用 IndexedDB 可以显着改善用户体验,但不正确使用或未能处理错误情况可能会导致应用程序损坏和用户不满意。

由于客户端存储涉及许多您无法控制的因素,因此至关重要的是,您的代码经过充分测试并能正确处理错误,即使是最初看起来不太可能发生的错误。