了解在 IndexedDB 与常用状态管理库之间同步应用状态的最佳实践。
当用户首次加载网站或应用时,通常需要执行大量工作来构建用于呈现界面的初始应用状态。例如,有时应用需要对用户进行客户端身份验证,然后发出多个 API 请求,才能获取在页面上显示所需的所有数据。
将应用状态存储在 IndexedDB 中,可以有效缩短重复访问时的加载时间。然后,应用可以在后台与任何 API 服务同步,并使用过时重新验证策略,延迟更新界面中的新数据。
不过,在使用 IndexedDB 时,有许多重要事项需要考虑,这些事项对于刚接触这些 API 的开发者来说可能并不明显。本文档解答了常见问题,并讨论了在 IndexedDB 中保留应用状态时需要注意的一些最重要事项。
让应用行为可预测
IndexedDB 的许多复杂性源于您(开发者)无法控制的因素太多。本部分探讨了使用 IndexedDB 时必须注意的许多问题。
写入存储空间的操作可能会失败
写入 IndexedDB 时出错的原因有很多,在某些情况下,这些原因超出了开发者控制范围。例如,某些浏览器不允许在无痕浏览模式下写入 IndexedDB。还有一种可能,就是用户所用设备的磁盘可用空间几乎已满,浏览器会限制您存储任何内容。
因此,请务必始终在 IndexedDB 代码中实现适当的错误处理。这也意味着,通常最好将应用状态保留在内存中(除了存储它之外),这样在无痕浏览模式下运行或存储空间不可用时,界面就不会中断(即使需要存储空间的其他一些应用功能无法运行)。
存储的数据可能已被用户修改或删除
与可以限制未经授权访问的服务器端数据库不同,客户端数据库可供浏览器扩展程序和开发者工具访问,并且可由用户清除。
虽然用户可能不常修改本地存储的数据,但清除这些数据的情况却很常见。请务必确保您的应用可以处理这两种情况,而不会出错。
存储的数据可能已过时
与上一部分类似,即使用户自己没有修改数据,他们存储的数据也可能是由早期版本的代码(可能是存在 bug 的版本)写入的。
IndexedDB 内置了对架构版本和使用其 IDBOpenDBRequest.onupgradeneeded() 方法进行升级的支持;不过,您仍需要编写升级代码,以便处理来自先前版本(包括存在 bug 的版本)的用户。
单元测试在这里会非常有用,因为手动测试所有可能的升级路径和情况通常是不可行的。
确保应用性能良好
IndexedDB 的一个关键特性是其异步 API,但不要因此误以为在使用它时无需担心性能问题。在许多情况下,不当使用仍可能会阻塞主线程,从而导致无响应。
一般来说,对 IndexedDB 的读写操作不应超出所访问数据的要求。
虽然 IndexedDB 可以将大型嵌套对象存储为单个记录(从开发者的角度来看,这样做确实非常方便),但应避免这种做法。这是因为,当 IndexedDB 存储对象时,它必须先创建该对象的结构化克隆,而结构化克隆过程发生在主线程上。对象越大,阻塞时间就越长。
在规划如何将应用状态持久保存到 IndexedDB 时,这会带来一些挑战,因为大多数热门状态管理库(例如 Redux)都是通过将整个状态树作为单个 JavaScript 对象进行管理来工作的。
虽然以这种方式管理状态有很多好处,并且将整个状态树作为单个记录存储在 IndexedDB 中可能很有吸引力且很方便,但在每次更改后执行此操作(即使经过节流/防抖处理)也会导致主线程不必要的阻塞,增加写入错误的可能性,在某些情况下甚至会导致浏览器标签页崩溃或无响应。
您不应将整个状态树存储在单个记录中,而应将其拆分为多个单独的记录,并且仅更新实际发生更改的记录。
与大多数最佳实践一样,这并非绝对规则。如果无法拆分状态对象并仅写入最小更改集,那么将数据拆分为子树并仅写入这些子树,仍然比始终写入整个状态树更可取。小小的改进总比没有改进好。
最后,您应始终衡量所编写代码对性能的影响。虽然对 IndexedDB 进行小规模写入的效果确实比大规模写入更好,但只有当应用对 IndexedDB 的写入操作实际导致长任务阻塞主线程并降低用户体验时,这一点才重要。衡量非常重要,这样您才能了解要优化的目标。
总结
开发者可以使用 IndexedDB 等客户端存储机制来改善应用的用户体验,不仅可以跨会话保留状态,还可以缩短重复访问时加载初始状态所需的时间。
虽然正确使用 IndexedDB 可以显著提升用户体验,但使用不当或未能处理错误情况可能会导致应用损坏和用户不满意。
由于客户端存储涉及许多您无法控制的因素,因此请务必对代码进行充分测试并妥善处理错误,即使是那些最初看起来不太可能发生的错误。