问题
昨天组员遇到了一个非常有意思的token
问题,在群里寻找答案,过程是这样的。 在浏览器打开两个页签A和B,分别登录A系统,此时两个页签属于同域名、同会话状态。 前端的token
是保存在storage
中的,同时在vuex
的store
中也保存了一份。
此时打开B页签的页面,退出又重新登录系统,storage
里面的token
会重新生成,由于B页签是重新登录的,所以,B页签的页面是可以正常加载成功的。
此时问题出现了,A页签的系统在文件上传时报错,提示token
失效,因为上传的token
是通过vuex
的store
进行获取的,B页签虽然重新登录了系统,但是A页签的store
数据并没有刷新,如何在不刷新A页签的情况下,及时更新Token
状态?
如何解决
大家可以思考一下,这个问题都有哪些解决办法。
监听 Storage
变化
给window
添加storage
事件,当localStorage
或者sessionStorage
值发生变化时执行。
js
onMounted(() => {
window.addEventListener("storage", function (event) {
console.log(
"Storage change: ",
event.key,
"from",
event.oldValue,
"to",
event.newValue
);
});
});
动画演示:
开启两个页签,分别打开同一个页面,右侧点击登录时,会给localStorage
写入一个token
,此时左侧页面监听到了数据变化。
如果是真实业务场景,我们就可以根据登录token
更新vuex
中的状态,从而避免老页面过期问题。
进一步扩展
假设,右边的登录和退车操作后,左边保持同步,如何实现?
js
<script setup>
import { ref, onMounted } from "vue";
const userName = ref("");
onMounted(() => {
localStorage.removeItem("token");
window.addEventListener("storage", function (event) {
console.log(
"Storage change: ",
event.key,
"from",
event.oldValue,
"to",
event.newValue
);
if (event.key === "token" && event.newValue) {
userName.value = "河畔一角";
} else {
userName.value = "";
}
});
});
// 登录
const login = () => {
userName.value = "河畔一角";
localStorage.setItem("token", "999");
};
// 退出
const logout = () => {
userName.value = "";
localStorage.removeItem("token");
};
</script>
我们在监听事件里面,根据token
值来判断用户状态,从而保持同步。
动画演示:
增加登录和退出按钮,定义userName
变量,登录后,给userName
赋值,并向localStorage
中写入token
,退出后,清空值。
监听事件里面可以根据token
的变化,控制userName
的值,从而实现左右页面同步。
注意: 大家通过动画演示应该发现了一个问题,为什么右侧页面的控制台没有打印值?是因为原始页面作为触发源,不参与事件监听,通过这个机制可以实现多个页面之间的通信。
使用 BroadcastChannel
BroadcastChannel 允许在相同的源在浏览器上下文(windows,tabs,frames或者iframes)之间进行简单的通信。
它有几个知识点:
- 创建频道:
new BroadcastChannel('channel')
- 发送消息:
channel.postMessage(message);
- 接收消息:
channel.onmessage = (data)=>{}
- 异常处理:
channel.onmessageerror = (e)=>{}
- 关闭频道:
channel.close()
代码示例:
js
<script setup>
import { ref, onMounted } from "vue";
const channel = new BroadcastChannel("myChannel");
const userName = ref("");
onMounted(() => {
channel.onmessage = function (event) {
console.log("Received message:", event.data);
if (event.data === "token is null") {
userName.value = "";
}else{
userName.value = "河畔一角";
}
};
});
// 登录
const login = () => {
channel.postMessage("token is 999");
userName.value = "河畔一角";
};
// 退出
const logout = () => {
channel.postMessage("token is null");
userName.value = "";
};
</script>
动画演示:
这个方案,其实就不需要去监听storage
的变化了,因为在退出的时候,可以直接发布一条消息,接收到消息以后,就可以更新页面状态了。
其他
web worker
主要用来做计算的,不能直接用于跨标签通信,不太适合。window.postMessage
主要用来实现跨页面通信,比如 A页面通过postMessage
给B页面发送消息,此场景也不太适合。websocket
固然可以实现,但用在这个场景岂不浪费。- 轮询:虽然也能勉强实现,但一般用在异步回调场景,此处也不适合。
总结
其实对于token
更新,本身一个非常小的问题,甚至都可以忽略,如果出现了,手动刷新一下页面即可。但从解决问题的角度出发,它的确是一个问题,那就需要考虑跨页签通信。
上面的两套方案,我反而觉得监听storage
是最简单的。如果你有更好的方案,可以贴在评论区,共同探讨。
感谢大家,我是河畔一角,一名普通的前端工程师。