一. 场景再现
- 前两天接到一个需求,要求实现类似于 B站 的那种,当我同时打开多个 Tab 标签的时候,如果我在某一个窗口退出了,那么其它窗口的登陆状态也需要同步退出。如下图,我同时打开了两个 tab 。
- 当我点击其中一个窗口的退出时,你会发现另外一个窗口也神奇的同步退出了。
- 经过查阅相关资料,比较简单的方法有两种,一个是 window.postMessage,另外一个就是监听 localStorage 的变化,接下来我会分别演示这两种方案。
二. 搭建一下基础样式
- 注意:样式方面,在这里我使用的是
UnoCSS
,将样式內联在了标签里,如果你还不了解这种写法,你可以点击下方的文章学习。不过即使你之前从未了解过UnoCSS
,也不会影响你下面的阅读,因为样式不是本文的重点,并不影响整体阅读。
手把手教你如何创建一个代码仓库 如果恰好你使用了
Unocss
那么你可以直接复制我下面的代码快速开始今天的知识。{{ isLogin ? "已登陆" : "已注销" }}- 如果你没有用到 Unocss,你也不用担心,因为我们的样式非常简单,页面只有三个元素。一个表示是否已经登陆的文案,然后剩下两个按钮,一个是打开新窗口的功能,一个是退出登陆的功能。
三. window.postMessage
- 我们快速书写一个打开新窗口的函数。
- 此时我们还需在当前页面挂载以后给 window 绑定一个事件来搭配 window.open 之后我们要做的事情。这里我们给 window 绑定了一个监听事件,事件的名称叫做 "message",回调函数中的参数 e 我们暂时不需要关系,我们继续往下进行代码书写。
可以看到我们的 window.open 正确的打开了一个新的 tab。 - 接下来我们编写我们的退出按钮的函数。
首先很简单,它把我们的 isLogin 变量标记为 false,我们就可以通过观察span
标签中的文案变化观察我们的状态。
另外一个重点,这里我们用到了 window.opener 这个属性,这个属性代表着它上一级的窗口。我们要向谁发生消息?上一级窗口对吧?调用target?.postMessage
函数,这个函数第一个参数就是我们要发送的消息。我们就用 “退出” 字符串当作我们退出的信号吧。第二个参数是我们用/
表示默认为当前的 origin 。届时上一级窗口的回调函数的事件对象就会收到我们的消息。 - 试验一下,可以很清楚的看到,我们第一个父窗口已经收到了来自子窗口的消息 “退出”,它是事件对象的 data属性的值。
那么此时我们就可以判断,如果收到了退出的信号,那么我也跟着把 isLogin 变量的值改为 false,也实现退出的动作。 - 测试一下效果:
四. 监听 localStorage
关于 storage 事件,这里面有一个误区,希望读者不要被误导,这个事件无法监听 sessionStorage 的变化。在 MDN 的中文文档中,并没有特别明显指出这一点。
而在英文文档中明确指出了这个十分关键的信息。
知道了这个关键点,就知道为什么我要写明要使用 localStorage 了。原文地址: MDN Storage Event
- 其实这个事件的用法和上面的 “message事件” 非常类似。第一步,你只需把我们给 window 绑定的事件替换为 "storage" 即可。
- 然后修改我们的 logOut 函数。在执行的时候在 localStorage 里写入一个 isLogin 为 false 的状态。(storage 只能写入字符串类型的值,所以需要 JSON 序列化一下,基础知识不过多赘述。)
- 可以看到,我们第二个 tab 退出的时候,第一个 tab 已经监听到了 storage 的变化了。
我们看一下这个事件对象身上的信息,这里面有两个属性是我们需要的。一个是 key,也就是发生变化的值,另外一个是 newValue 代表我们刚刚设置的值。 - 知道了这些信息,我们就可以在事件的回调函数中做一些处理。
- 测试一下:
五. 总结
实现方法其实还挺简单的,在实际工作中,我们项目的实现就是将 token 存放到 localStorage 里,通过监听 token 的存在来实现多 tab 的同时退出和同时登陆。
其实还有别的方法,比如同时开启一个 webSocket,让后端搭配同步向 tab 发消息等。