利用 indexedDB 保持应用状态的最佳实践

    当网站或应用首次被加载时,首先通常需要准备大量工作去构造初始的应用状态信息,然后再使用这些信息去渲染界面。例如,有的应用需要用户进行身份验证,之后才能去发一些 API 请求获取数据,最后才是将返回的数据呈现到页面中。

    可见,如果把应用状态信息存储到 indexedDB 中,将能够提升一些频繁访问页面的加载时间。通过 indexedDB 还能够采用失效更新策略(stale-while- revalidate strategy),在后台与任何 API 服务进行同步,只有当数据更新时才会去更新 UI。

    然而在使用 indexedDB 时,仍然有很多重要的事情需要考虑,开发者对这些新的 API 也不一定非常熟悉。因此这篇文章将回答一些 indexedDB 的常见问题,并且讨论在使用 indexedDB 保持应用状态时需要关注的一些重点。

    造成 indexedDB 复杂的原因在于,应用有很多方面开发者无法完全控制。本章节将探讨在使用 indexedDB 时必须牢记的许多问题。

    因此,在操作 indexedDB 的代码中执行错误处理操作变得至关重要。这也意味着将应用程序状态保存在内存(除了存储它)中是个好主意,这样在各种存储失败的情况下,UI不会被中断。

    不像服务端数据库那样开发者可以限制未经授权的请求,客户端数据库可以被浏览器扩展插件和开发者工具所访问,并且用户可以清除它们。

    虽然用户修改本地存储的数据并不是很常见,但用户清除它们却是很常见的。因此应用程序需要控制好这类可能出现的情况以确保不会发生错误。

    与上一节类似,即使用户本身没有修改过数据,存储的数据也可能是由旧版本的代码编写的,而这些旧版本的代码中可能包含错误,因此存储的数据还可能是由包含 bug 的代码写出的带有问题的数据。

    indexedDB 内置支持了模式版本(schema version),并通过 方法进行升级;但是,你仍然需要编写升级策略的代码,以便可以处理来自以前版本的用户(包括具有错误的版本)。

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

    作为一般规则,indexedDB 的读写不应该大于待访问数据的大小。

    虽然 indexedDB 可以将大型嵌套对象存储为单个记录(这样做从开发者角度来看是非常方便的),但是应该避免这种做法。原因是因为 indexedDB 在存储一个对象时,首先必须创建该对象的,这个克隆过程会在主线程中进行。因此对象越大,阻塞时间越长。

    这就给规划如何将应用状态保留到 indexedDB 带来一些挑战。因为大多数流行的状态管理库(如 Redux)会通过整个状态数作为单个 Object 进行管理。

    虽然这种方式管理状态有很多好处(例如它将使您的代码易于推理和调试),同时将整个状态树作为单一记录存储到 indexedDB 中可能会非常诱人和方便,但是在每次改动之后(即使使用了 /)将导致主线程不必要的阻塞,这将增加写入错误的可能性,并且在某些情况下甚至会导致浏览器选项卡崩溃或无响应。

    所以应该将整个状态树分解为单个记录进行存储,并且只更新实际更改的记录,而不是将整个状态树都存储到单个记录中。

    与大多数最佳实践一样,这并不是一个要么全有要么全无的规则(all-or-nothing rule)。在分解状态对象并且写入最小变更集不可行的情况下,将数据分解为子树,只写入这些数据仍然优于写入整个状态树。一点点改进比没有改进更好。

    最后,你应该。尽管对于 indexedDB 的少量写入比大量写入性能要更好,但应该只有当 indexedDB 的操作是导致主线程阻塞并降低用户体验时的主要原因时,才需要着手去做相关性能优化。只有通过测试,才能够让你了解代码真正需要优化的部分。

    开发人员可以利用像 indexedDB 这样的客户端存储机制,不仅能够在会话中持久化状态,还可以减少在重复访问时加载初始状态所需要的时间,从而改善用户的应用体验。

    需要注意的是,尽管正确使用 indexedDB 可以显著提高用户体验,但是在使用错误或者无法处理错误的情况下可能会导致应用损坏并且影响用户体验。