「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

一种简单无副作用的同源跨页面数据同步方案

2021 年 1 月 29 日

一种简单无副作用的同源跨页面数据同步方案

背景


提起这个方案,还要从某个风和日丽的早晨说起。那日小编正忙着手上的各种需求,突然后端的亲火急火燎的找到小编,说是有一个重要的用户,在使用 Word 在线编辑文档功能时,发现保存的文件被篡改了。一听到这,我心想这下摊上事儿了,妥妥的线上故障,但还是故作镇定的开始排查是什么问题。


经过了日以继夜的排查后,小编发现是由于用户同时打开了两个在线编辑页面,并且在 A 页面的在线编辑工具还未关闭的情况下,去 B 页面也打开了在线编辑工具。


说到这个在线编辑工具,它叫 pageOffice,当他在线被触发启动时,会在本地打开一个类似软件的窗口,启动一个相对独立的服务。且这个服务前端通过 Web SDK 提供的 API 能进行控制的余地非常小,唯一的通信方式只有 pageOffice 中操作触发页面上的回调函数。在和 pageOffice 的客服进行了一系列如同太极的沟通后,我们还是没能解决如何知道用户已经打开了 pageOffice 并且阻止用户在另一个页面触发打开工具的方法。


初探


上文提到的, pageOffice 打开以后就成为了相对独立的个体,于是乎,小编对它直接的各种软磨硬泡都宣告失败。进而小编放弃了探索对它的控制,转而思考两个页面之间通信的控制。


平时咱们对一个方法是否运行过,最常用的方式就是 “状态开关”。即存储用一个变量,类似于  ifOpen 之类的,将其设置为 ture 去记录当前方法已运行,再在其运行结束时设置为 false,即可完成一个闭环。而我们这次除了以上条件,还需要让别的页面也拥有这个变量,才能阻止别的页面在这个方法运行时再次触发这个方法。这听起来有点绕,不过下面有一个小图解来解释我们这次问题的初步解决方案。



显而易见的,此处应有一个跨页面通信的方案,但是由于这是同一个页面上的功能,所以我们可以选择最简便的方案。


提到跨页面数据存储,聪明的你们肯定会想到本地存储 localStorage,提到localStorage 小编就会想起它的兄弟 sessionStorage,那就大致回顾一下它们两的特性吧:


  • localStorage:持久的,相同的协议、主机名、端口(同源)能增删改查,数据不会自动清除;

  • sessionStorage:临时的,除了同源外还要在同一窗口下才能增删改查,数据会在窗口关闭时自动清除。


看到这里想必大家已经看出来,本地存储 localStorage 完全可以满足上图中描述的功能。但是回想一下题目中提到的 副作用 一词,大家是否心中暗想此事必不简单。


小编解释一下:首先,由于 localStorage 不会自动清除的特性,当用户再次进入页面时,之前保存的 localStorage 里的数据会还在;其次,之前提到过,pageOffice 打开后就独立了,所以,这两个条件结合后就存在这样一个场景 —— 在 pageOffice 还在打开的时候,用户先把页面关闭了,之后再关闭 pageOffice,此时,页面已经不存在了,所以 pageOffice 关闭时触发的回调函数,此时已经通知不到页面去改变存在 localStorage 里的变量。而再下一次打开页面时,由于localStorage 存的数据还是上次未关闭 pageOffice 时的 ifOpen = false, 所以,如果用户不自主清除本地缓存,将再也打不开 pageOffice,小编把这种关闭页面在未来可能会造成负面影响的数据称为 副作用


构思


为了清除上述方案带来的副作用,小编废寝忘食围绕副作用删除的时机想到了几种方案:


方案一:用 localStorage 储存一条有当前打开页面 Id 的数组,当页面关闭就过滤掉关闭页面的 Id,关闭页面直到最后数组长度为 1,并且 Id 就是当前页面的 Id 时,就清除掉localStorage 中副作用的数据。


这个方案的缺陷就是,我们无法确定页面的关闭时机,现有的在页面关闭时能触发的事件是 beforeunload,但是非常不理想的是,这个事件在页面刷新的时候也会触发,如果刷新页面则会产生预期外的效果,这并不是我们想要的,即使在这个事件中区分当前触发的是刷新还是关闭也是不太合理的,所有最后还是选择更换别的方案。


方案二:由于关闭页面的时机无法确定,所以小编考虑将其转存为页面上的变量或者换一种储存方式。

查阅了和 localStorage 有关的内容之后,发现现存有这么一个神奇的事件叫做 storage 事件,仔细阅读关于这个事件的相关文献后发现其有几个特点:


  • 首先,它需要在同一浏览器打开两个同源的页面

  • 其次,两个页面都注册了这个事件,并且有 localStorage 的变化,事件在其他页面返回最新变化的 localStorage 的 Key 和 Value

  • 最后,这个事件并不是用来监听当前页面自己的 localStorage 变化的


看起来这个事件完全就是考虑到了我们转存 localStorage 准备的,小编心里顿时就觉得怎么会有这么善解人意的事件。


虽然有了这个事件的存在,但是我们该如何顺利的帮助 localStorage 转型呢?


回想起上文提到的 sessionStorage 这个会话存储,一想到它能够在窗口关闭时自动清除,小编就想用它搞点事情。顺便一提,页面上的变量也是可以在页面关闭时自动清除的,不过当没有两个页面的时候,这种事件触发的变量一刷新就会丢失,但是 sessionStorage 刷新还是会保留在当前页面存储中,于是,小编就萌生了这样一个 localStorage 和 sessionStorage 联合使用的想法。


实现


这个方案最终的目的就是要把 localStorage 中的数据都转到 sessionStorage,简单来说也就是跨页面的 sessionStorage 的数据同步,而 localStorage 就是我们跨页面的一座桥梁。所以,方案基本的实现原理就是:当数据变化时,我们首先要做的就是把数据存在当前页的 sessionStorage 里,并触发一次 localStorage 的变化即存一次数据到localStorage 里,通过 storage 将数据运输到另一个页面。


值得注意是,localStorage 的转型就是为了删除副作用,所以当把数据存入localStorage 后,下一步就是直接清除存入 localStorage 里的数据。


在这里小编封装了一个函数,数据传的是一个对象,这样就可以一次同步多个数据啦,先进入一下图解环节,让大家有个初步的理解。



原理函数:


// 触发事件,需要同步数据变化时的事件function setSessionStorage(payload) {  const data = JSON.stringify(payload);  // 同步当前页面数据变化  sessionStorage.setItem('setSessionStorage', data);  // 触发localStorage的change事件将数据同步到其他页面  localStorage.setItem('setSessionStorage', data);  // 删除副作用  localStorage.removeItem('setSessionStorage');}
复制代码


然后就是我们的桥梁 storage 核心事件的实现:


由于把数据存入 localStorage 后,下一步就是直接清除存入 localStorage 里的数据,清除 localStorage 也是会进入这个函数的,只要校验此时的值为空时不将数据同步即可。


// 监听的storage变化的事件function storageChange(e) {  // 校验null是为了在清除localStorage时不产生效果  const ifNull = e.newValue === null && e.newValue === 'null';  // 获取从别的页面传递过来的数据,并将数据同步到当前页  if (e.key === 'setSessionStorage' && !ifNull) {    sessionStorage.setItem('setSessionStorage', e.newValue);  }
// 页面初始化时触发一次change事件将数据同步到其他页面 if (e.key === 'getSessionStorage' && !ifNull) { // 获取当前页的sessionStorage const currentSessionStorage = sessionStorage.getItem('setSessionStorage'); // 其他页面初始化时,已存在的标签页会触发getSessionStorage事件 // 将sessionStorage储存在localStorage并触发其他页面的change事件,同时传递参数 localStorage.setItem('setSessionStorage', currentSessionStorage); localStorage.removeItem('setSessionStorage'); }}
复制代码


这里还有一点要注意的是,我们同源跨页面的场景一般两个页面都不是同时开启的,又由于我们删掉了 localStorage 里的数据,所以,在另一个页面打开时,我们需要进行一次数据的同步,这就是上文的 storage 事件中下部分函数的功能。这部分可能会有点绕,所以小编还是贴心的准备了一份图解供大家参考。



初始化函数部分:


function init() {  // 初始化监听localStorage的change事件  window.addEventListener('storage', storageChange);  // 页面初始化时触发一次change事件将数据同步到其他页面  localStorage.setItem('getSessionStorage', 'any');  // 删除副作用  localStorage.removeItem('getSessionStorage');}
复制代码


最后,不管在页面哪个地方,只要不关闭窗口,只需要一行获取当前 sessionStorage 的代码即可。


// 当前sessionStorage储存的数据const currentSessionStorage = sessionStorage.getItem('setSessionStorage');
复制代码


这样,一种简单无副作用的同源跨页面数据同步方法就实现啦~



头图:Unsplash

作者:凯译

原文:https://mp.weixin.qq.com/s/Q90HS8UWbBkFSSdkN8gkVA

原文:一种简单无副作用的同源跨页面数据同步方案

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021 年 1 月 29 日 22:412439

评论 1 条评论

发布
用户头像
。。。 其实,服务器端保存同一个文件的多个版本(副本)就好了啊,先进的系统都是这样做的
2021 年 02 月 07 日 10:22
回复
没有更多了
发现更多内容

架构师训练营第八周总结

邓昀垚

简析低代码开发与传统开发的区别与优势

Marilyn

敏捷开发 低代码

mongodb 源码实现系列 - 网络传输层模块实现三

杨亚洲(专注mongodb及高性能中间件)

MySQL mongodb 分布式 高性能 分布式数据库mongodb

LeetCode题解:77. 组合,递归回溯,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

【涂鸦物联网足迹】API及SDK介绍

IoT云工坊

软件开发 物联网 API sdk 云平台

会展云技术解读 | 面对突发事故,APP如何做好崩溃分析与性能监控?

京东科技开发者

云计算 云服务

技术分享:WebAssembly能否重新定义前端开发模式?

Geek_Willie

webassembly

移动安全加固助力 App 实现全面、有效的安全防护

蚂蚁集团移动开发平台 mPaaS

安全攻防 App风险 mPaaS

阿里云官方推出操作系统“等保合规”镜像 -- Alibaba Cloud Linux 等保2.0三级版

阿里云基础软件团队

内核

DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座

华章IT

数据库 postgresql

如何实现后台管理系统的权限路由和权限菜单

徐小夕

Java 编辑器 H5 数据可视化 前端进阶

浅谈API网关(API Gateway)如何承载API经济生态链

华为云开发者社区

API 网关

分库分表的 9种分布式主键ID 生成方案,挺全乎的

程序员内点事

分库分表 Java 分布式

揭秘在召唤师峡谷中移动路径选择逻辑?

华为云开发者社区

算法 地图 最短路径

《Python:Python编程简介:计算机编程和机器学习入门指南》

计算机与AI

Python

谈谈敏捷开发概念和迭代开发方案

Learun

敏捷开发

go-zero如何追踪你的请求链路

万俊峰Kevin

go Trace microservice

重磅解读:K8s Cluster Autoscaler模块及对应华为云插件Deep Dive

华为云开发者社区

容器 k8s 服务

每周一看:16份文档资料,程序员软硬实力全概览,总有一个适合你

小Q

Java 学习 程序员 架构 面试

医疗界“最强大脑”落户杭州!阿里巴巴联合浙大一院共同打造

阿里云情报局

互联网

帮助企业摆脱困境,名企归乡工程师:能成功全靠有它!

Philips

敏捷开发

架构师训练营 - 第 7 周课后作业(1 期)

Pudding

接口测试如何在post请求中传递文件

测试人生路

接口测试

嗯,查询滑动窗口最大值的这4种方法不错...

王磊

Java 数据结构和算法

【云小课】版本管理发展史之Git+——代码托管

华为云开发者社区

git 代码管理 托管

Apache DolphinScheduler 是如何走进Apache的

海豚调度

大数据任务调度 数据湖调度 DolphinScheduler Apache DolphinScheduler

【运维思考】如何做好云上运维服务?

嘉为蓝鲸

云计算 运维 数字化转型 数据中心 云服务

“开源软件供应链点亮计划-暑期2020”公布结果 基于ChubaoFS开发的项目获得最佳质量奖

京东科技开发者

大数据 云原生 开源项目

天啦撸!打印日志竟然只晓得 Log4j?

沉默王二

Java 日志 log4j

终于啃完了这份Java核心原理+框架“面试圣经”,成功五面上岸美团

Java架构追梦

Java 架构 面试 微服务 框架开发

解决大中型浏览器(Chrome)插件开发痛点:自定义热更新方案——2.基于双缓存更新功能模块

梁龙先森

Java chrome 浏览器 技术方案 前端进阶

一种简单无副作用的同源跨页面数据同步方案-InfoQ