前端离线化

前端离线化方案

Application Cache

HTML5 最早提供一种了一种缓存机制,可以使 web 的应用程序离线运行。我们使用 Application Cache 接口设置浏览器应该缓存的资源,即配置 manifest 文件, 在用户处于离线状态时,点击刷新按钮,应用也能正常加载与工作。不过该接口很快被标准废弃了,原因之一是这是个设计很不合理的接口,比如更新不及时,无法做到用 javascript 精细化控制,可用性很差,如果你不严格的遵循其规则,会遇到很多坑。取而代之的是更强大的 service-worker。

service-worker

正因为 Application Cache 一直无法有效的解决离线资源精细化控制,service-worker (以下简称 sw)接口被设计出来了,比起 Application Cache,它提供独立的后台 JS 线程,是一种特殊的 worker 上下文访问环境。在渐进式 web 应用 PWA 中,sw 为 Network independent 特性提供了最核心的支持。借助 CacheStorage,我们可以在 sw 安装激活的生命周期中,按需填充缓存资源,然后在 fetch 事件中,拦截 http 请求,将缓存资源或者自定义消息返回给页面。service-worker 实现了真正的可用性及安全性。首先,相对于原有 web 应用逻辑是不可见,它类似于一个中间拦截服务,中间发生任何错误,都会退回到请求线上逻辑。其次,它只能在 https 下运行保证了安全性。

离线数据方案

大部分离线场景将是会在本地独立 app 之中,借助客户端能力,我们可以把 web 代码包提前内置到客户端之中,然后使用一套代码更新机制,前端代码缓存问题可以得到解决。离线化方案的复杂度之一在于离线数据的处理,及如何对设计之初就没有考虑过“Offline First”的旧代码进行最小改造处理,既优先考虑在离线状态的基本功能,在线时再进一步增强。基于离线和在线逻辑解耦的考虑,我们应该本着最大限度减少对原有在线逻辑侵入的原则去思考离线化方案。

PouchDB

PouchDB 是一个跨平台 javascript 数据库,内部封装了 IndexDB、WebSql 兼容前端处理.一般而言前端 pouchDB 进行离线处理,搭配后台 CouchDB,可以更方便双向数据同步。Sync 接口专门用不同步前后的数据:在中小型项目,特别是那种后台可以由前端接手的全栈式开发,pouchDB 是一种不错的离线数据处理方案。此方案问题是压缩后任然有 130 多 kb,并且依赖于特定后台方案,不够通用。

Redux-Offline

对于项目使用了 redux 数据管理的项目而言,最快捷的办法,就是使用 redux-offline,其基本思路是通过 redux middleware 监听每次 acton 数据变化,然后将需要离线的数据序列化到本地(对于 web 浏览而言存储兼容顺序是 indexdb — websql — localstorage),等下一次刷新页面时,优先从本地还原数据还原到 store 中。这种方案的好处是快速配置需要缓存的 API 接口到中间件即可,充分结合了 redux 特性,对于想要达到简单优先展示离线数据的应用而言,是非常不错的。但这种思路带来的问题是操作数据不够灵活,本地储存数据无法方便的和其他非 redux 逻辑共享。在离线数据量较大的情况,一次性读写,并同时序列化大量本地数据也会带来性能问题,对于频繁有数据变更的场景也不合适。

Redux 与 IndexDB 结合

如果想要达到对数据精细化控制,并且同时不对原有在线逻辑有过多的侵入,我们可以在数据储存上用 IndexDB 替换后台返回数据,前端数据处理仍然复用原有 redux。业务数据的本地储存需要注意的就是合理抽象业务使用的数据,然后按照数据库设计的基本原则本地建表。对于前端代码架构上,单独抽出一层 redux 中间件,通过配置文件的形式,将需要离线的 API 初始化时传进去,然后在 middleware 中,完成对 DB 的读写操作,将数据组装好给下一个 reducer,可以叫 offline 中间件。为了更进一步合理的对 api 参数分解出来,也需要在 offline 中间件前将接口请求层再抽象一个中间件,叫 API middleware ,这样经过离线中间件的 api 参数已经被分解,可以直接作为查询 db 使用,同时也能服务于后台请求。

离线优先与数据同步

我们已经可以通过配置将需要离线的接口通过 offline 中间件进行离线化,那么这里面临着两种数据更新方式:

  1. 在线时走正常逻辑等待后台数据返回,异步同步到本地数据库,再进行渲染;当判断离线时,从本地读取。

  2. 具备乐观 UI 的思维,配置了离线的接口,优先从本地进行数据操作,渲染 UI,然后再将服务端数据与本地数据进行同步。

显然,后者离线优先的方案显得更为明智。数据同步分为本地向后台同步,和后台向本地同步。后者需要增加增量变更的逻辑,用于解决离线下用户数据由于其他原因发生的变更,比如当用户登录多台设备数据移动、删除等场景。如何记录本地的数据变更然后同步到后台呢?这里我们需要定义一个数据变更的抽象,比如 Change 里面功能主要是定义变更类型,字段等。每次抵达 offline 中间件的数据通过一个数据同步管理器对变更进行注册,待合适的时机再去同步。数据同步管理器主要接受 change,进行 diff 管理,判断数据是否有变化,及去重管理,最后再触发异步同步任务。同步可能会失败,这里的超时,重试,失败退回处理都需要加以注意,保证同步的事务性。

储存安全

储存安全包括数据加密安全和储存大小问题。对于对称加密,前端查看,客户端必须要知道密匙,密匙本身绕不开加密的问题,理论上,不和服务端通信的离线状态,任何能够在前端能够离线下查看的数据,不管采用什么加密手段,数据都能被还原。纯前端数据加密并无可靠性, 但是访问权限可以依赖于 IndexDB 浏览器同源策略进行数据安全隔离。避免明文储存和加大数据直接还原的难度才是思考的方向。 采用 IndexDB 的储存方案涉及到一个储存大小问题,浏览器的最大存储空间是动态的,总共为可用磁盘空间的 50%,每个站点为所用空间的 20%,超出限制的写入将导致数据被删除,并且会导致严重在的数据丢失。因为从浏览器本身无法直接获取到 IndexDB 储存空间。

总结

离线化是很多前端项目不会设计进去的特性,因为对于大部分纯展示型 web 项目而言,它的收益性价比低。但作为工具型,创造型应用而言,离线会是一个具有长期受益的特性。

最后更新于