问题
昨天组员遇到了一个非常有意思的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是最简单的。如果你有更好的方案,可以贴在评论区,共同探讨。
感谢大家,我是河畔一角,一名普通的前端工程师。