无感数据交互 - 概览
无感数据交互是指用户在与应用进行交互时,无需等待即可立即展示相关内容,或者提交信息时也无需等待即可展示操作结果,就像和本地数据交互一样,从而大幅提升应用的流畅性,它让用户感知不到数据传输带来的卡顿。
这并不是新鲜事,早在 2015 年以前就已经有乐观更新的概念了,它是指在服务端响应前将提交结果展示到界面中,它是建立在大部分的提交都成功的假设上,与之相对的是保守更新,即服务端响应前将展示等待状态,直到请求完成。在处理失败方面,目前的乐观更新方案通常是通过回退来处理,例如下面的示例代码:
const list = [];
const data = {};
addTodo(data).catch(() => {
list = list.filter(item => item !== data);
});
list.push(data);
这可能会导致以下问题:
- 回退将增加用户的理解成本和操作成本;
- 请求时序问题;
- 如果后续的请求存在对本次提交依赖,则本次失败将导致后续的请求变得没有意义;
- 可能丢失请求;
经过数个月的方案设计和不断迭代,alova 已经在这方面走出了一大步,在我们的方案中对以上存在的问题进行了解决,可以更稳定地保证请求成功,虽然还存在技术限制,但在许多场景中得到了应用。在我们的技术方案中,可以更高限度地降低网络波动带来的问题,你的应用在高延迟网络甚至是断网状态下依然可用,在刷新页面后也依然能保持最新状态的数据。
应用场景
无感数据交互虽然不能大规模地使用,但在一定场景下它又是非常合适的,在探索中,我们发现了至少包含但不限于以下几个场景,供你参考。
编辑器类应用
笔记类应用如印象笔记,画布编辑类应用如 MasterGO,它们分别需要实现以下需求:
- 进入笔记或图纸列表时将全量拉取列表数据,下次进入将使用本地的缓存数据;
- 编辑过程中实时同步到服务端,且同步过程发生在后台,不会影响用户正常使用;
- 网络差或断网状态下也可以继续使用;
设置模块
由常用的开关、选择器组成的设置模块,需实现的需求是,用户操作后实时同步到服务端,同时不再展示提交状态,而是直接展示操作后的最新状态。
简单的列表管理
我们将创建列表项时填写的数据足够用于列表页的展示,称为简单的列表,例如一个学生列表页展示学生的姓名、性别、年级三个数据,这三个数据在创建学生时都需要填写。在简单列表中将实现以下需求:
- 在新增、编辑和删除列表项时立即在列表页中展示最新状态,无需在提交完成后才迟迟展示,同 时也不受网络波动限制;
- 刷新页面时,列表页始终保持最新状态;
复杂的列表管理
复杂列表是指,创建列表项时填写的数据不足以在列表页用于展示,而需要根据服务端的计算产生额外的数据,例如一个 Todo 列表页除了展示基本信息外,还需要列出具体的执行日期,而在创建页只指定了执行日期范围和相关规则,因此执行日期由服务端根据日期范围和规则统一计算生成。
在复杂列表中将实现以下需求:
- 在新增、编辑和删除列表项时立即在列表页中展示最新状态,并在服务端响应后将服务端计算的数据更新到此列表项中;
- 刷新页面时,列表页始终保持最新状态;
复杂列表示例敬请期待...
自由模式
在以上的几种场景下,你可能希望根据一个条件来判断当前是使用无感交互策略还是最常见的保守请求策略,需求如下:
- 当网络状态良好时, 或付费用户将使用无感交互策略,而网络波动大时,或免费用户则不能享受到无感交互策略;
- 策略自由切换;
在以上的示例中你都可以体验自由切换策略
不推荐的应用场景
信息共享类
提交的信息需要同步给其他人,例如订单信息,这类信息具有高实时性的要求,我们应该确保提交成功。
复杂的数据交互类
复杂的数据交互是指,数据的编辑和筛选混合进行,例如对某个列表混合进行新增、编辑、删除和筛选,在这种情况下 alova 目前还无法很好地支持,在后续的版本中也将尝试解决这个难题。
技术方案
在无感数据交互的技术方案上,alova 分别实现了数据预拉取和静默提交,接下来我们来了解这两种技术方案。
阅读前请确保已经掌握了以下章节内容
数据预拉取
在 html 中你可能见过这样的标签<link rel="prefetch" href="index.css" as="style">
,它告诉浏览器在闲时去预加载样式文件,并放在缓存中,当真正需要使用的时候从缓存中取出即可,alova 也使用了类似的方案,通过useFetcher来预拉取需要的数据,它将保存在本地缓存中。你可以在任意情况下预判用户的需要阅读的内容,然后预先拉取对应的内容,如在流程类的页面中可以预加载下一页的内容,又或者,用户在某个按钮上停留了 200ms,我们可以预先拉取下一个界面需要的数据,这点类似于Next.js的页面预加载。
静默提交
静默提交是一种提交即响应的机制,方案中将保证提交的完成,因此可以将它看作更安全的乐观更新方案。静默提交主要通过静默队列来持久化请求信息,以及保证请求时序问题,通过虚拟数据来作为服务端响应数据的占位符,当请求完成后替换为实际的响应数据,通过这两项技术实现了本地化的数据创建,并对新建数据进行编辑、删除等操作,即使创建的数据还未真正在服务端提交成功。为了让开发成本降到最低,这些在 alova 中都是自动完成的。
静默队列
静默队列用于保证请求时序问题,我们可以任意创建队列,进入队列的请求都将会以SilentMethod实例的形式保存在队列中,每个SilentMethod除了包含请求信息外,还包含静默提交的相关配置,如唯一 id、错误重试参数等。队列内的请求只有在前一个响应后才会发起下一个请求,从而保证队列内的请求时序。你可以将有依赖关系的请求放到同一个队列中,这样也可以保证数据的一致性。
在方案中,还分别提供了queue
、silent
、static
三种行为模式,用于区分一个请求需要以怎样的行为进行请求。
- queue:请求将进入静默队列,但不会被持久化,将等待前面的请求完成才发送请求,响应回调将会在响应后触发,一般用于需要依赖前项请求的数据获取;
- silent:请求将进入静默队列,且会被持久化,然后立即触发响应回调,这种行为模式下,onSuccess 将接收到虚拟数据,onError 永远不会被触发,在进行提交即响应的场景下需使用此模式;
- static:请求不会进入静默队列,也不会被持久化,它会立即发出请求,在禁用静默提交时可使用此模式;
虚拟数据
在提交即响应的机制中,虚拟数据起到了重要的作用,它表示在服务端真正响应之前,作为响应数据的替代数据进行占位,并通过追查机制,虚拟数据即使分布在应用各个位置,也能在响应后自动替换为实际的响应数据。同时在静默队列中也起到了重要作用,它可以标识队列内请求的依赖关系,并在依赖项响应后将依赖数据替换为实际数据,例如创建一条数据时将返回这条数据的 id,当服务端还未响应时,用户又进行了删除操作,需要将 id 作为删除标识,此时删除请求将依赖创建请求。在创建请求响应前,虚拟数据将作为 id 占位符作为删除的参数,并在创建请求响应后替换虚拟数据 id,至此就可以完成删除的请求。
接下来,我们将会具体了解虚拟数据的特性。