本文翻译自 。
在网络不佳的情况下表单的表现并不理想。如果提交表单的时候恰好断网了,辛苦填好的数据有可能就找不回来了。下面就分享一下我们是如何修复这个问题的。
先睹为快,。
自从有了 Service Worker,开发者可以为用户提供离线版的 Web 应用。静态资源的缓存相对比较容易,但对于需要和服务端交互的表单来说就有点困难了。不过还好,我们还是有办法为离线提供一种 fallback 的方案。
首先,创建一个与离线友好表单相对应的 Class。将表单的 id
、action
保存下来,并绑定表单的 submit
事件。
class OfflineForm { // setup the instance. constructor(form) { this.id = form.id; this.action = form.action; this.data = {}; form.addEventListener('submit', e => this.handleSubmit(e)); }}复制代码
在 submit 的处理函数中,可以使用 navigator.onLine
来检查网络链接情况,这个 API ,就算要实现也比较简单。
但是这个 API 。因为这个属性只能表示存在网络链接,并不保证网络是通的。反过来,如果结果是 false
就可以明确表示是断网的。因此据此判断是否离线是一种相对靠谱的方式。
如果用户处于离线状态,我们将表单提 hold 住,把表单数据保存在本地。
handleSubmit(e) { e.preventDefault(); // parse form inputs into data object. this.getFormData(); if (!navigator.onLine) { // user is offline, store data on device. this.storeData(); } else { // user is online, send data via ajax. this.sendData(); }}复制代码
保存表单
我们有。根据数据的特点,你可以使用 sessionStorage
,如果不想把数据存储在内存种,也可保存在localStorage
中。
给表单数据付上一个时间戳,挂在一个新对象上。然后使用 localStorage.setItem
保存。这个方法接受两个参数,key(表单 id) 和 value(通常是一个 JSON 字符串)。
storeData() { // check if localStorage is available. if (typeof Storage !== 'undefined') { const entry = { time: new Date().getTime(), data: this.data, }; // save data as JSON string. localStorage.setItem(this.id, JSON.stringify(entry)); return true; } return false;}复制代码
注意:可以在 Chrome 的开发者工具的 Application 标签中查看 storage 数据。如果不出差错,里面的内容如下:
还有最好将离线的情况告知用户,告诉他们填写的数据并不会丢失。补充 handleSubmit
方法,将这些信息反馈给用户。
考虑得真是周到呀!
检查保存的数据
一旦用户联网,就需要检查一下本地是否存在未提交的表单。我们需要监听 online
事件跟踪网络链接的变化,或者页面刷新触发的 load
事件。
constructor(form){ ... window.addEventListener('online', () => this.checkStorage()); window.addEventListener('load', () => this.checkStorage());}复制代码
当这些事件触发时,我们只需检查在 storage 中是否存在与表单 id 相匹配的数据。根据表单数据类型的不同,可能需要添加一个过期时间的判断,只允许在特定的时间内的表单。这一点对于偶尔网络链接异常的情况很有效果,避免错误地把两个月以前保存在本地的表单提交。
checkStorage() { if (typeof Storage !== 'undefined') { // check if we have saved data in localStorage. const item = localStorage.getItem(this.id); const entry = item && JSON.parse(item); if (entry) { // discard submissions older than one day. (optional) const now = new Date().getTime(); const day = 24 * 60 * 60 * 1000; if (now - day > entry.time) { localStorage.removeItem(this.id); return; } // we have valid form data, try to submit it. this.data = entry.data; this.sendData(); } }}复制代码
如果表单成功提交,则还需要最后一步,将表单数据从 localStorage
清除。比如说一个 Ajax 表单,我们可以在服务端成功返回后马上实施清除动作。这里简单使用 storage
removeItem()
方法即可。 sendData() { // send ajax request to server axios.post(this.action, this.data) .then((response) => { if (response.status === 200) { // remove stored data on success localStorage.removeItem(this.id); } }) .catch((error) => { console.warn(error); });}复制代码
如果实在不想使用 Ajax 提交,另外一种处理方案就是将数据回填表单,直接调用 form.submit()
提交,或者让用户自己点击提交按钮提交。
注意:简单起见,我略去了一些环节,比如表单验证以及安全 token 验证等等。这些步骤在真正的产品开发中是必不可少的。还有一个问题是关于敏感数据的处理,避免在本地明文存储用户密码信用卡信息等等。
如果有兴趣,可以。