Vue3 跨标签页或跨窗口通信
在 Vue 应用中,跨标签页或跨窗口的通信通常涉及到两个或多个浏览器标签页之间的信息共享。由于每个标签页或窗口都是独立的 JavaScript 执行环境,它们不能直接通过 Vue 或其他 JavaScript 库来直接相互通信。但是,有一些方法可以实现这种跨标签页的通信,主要依靠浏览器提供的 Web API。
以下是一些常用的跨标签页通信方法:
- 使用 localStorage 和 storage 事件
- 使用 Broadcast Channel API
LocalStorage storage 事件
LocalStorage 提供了一种简单的跨页面、跨标签页通信机制。不同标签页或窗口可以通过 localStorage 存储信息,而监听 storage 事件的方式可以在其他标签页检测到这些变化。
通过 localStorage 存储数据
const count = ref(0);
function onSend() {
count.value++;
let msg = { message: "LocalStorage 消息:" + count.value };
localStorage.setItem("message", JSON.stringify(msg));
state.sData.push(msg);
}
监听 storage 事件
onMounted(() => {
// 监听 LocalStorage 变化消息
window.addEventListener("storage", function (e) {
if (e.key === "message") {
state.aData.push(JSON.parse(e.newValue));
}
});
});
在同一页面使用
window.addEventListener("storage", function (e) {})
是无效的。
使用 LocalStorage 或 SessionStorage 进行跨标签页数据通信的优点是它们是同步的,并且不需要任何网络通信。然而,这些方法不适用于大型或复杂的数据结构,并且它们可能会引起性能问题,尤其是在存储大量数据或频繁更改数据时。
Broadcast Channel API
Broadcast Channel API 是一个用于在不同窗口或标签页之间进行通信的 API,它允许一个页面向另一个页面发送消息,这些页面可以在同一浏览器实例中打开,或者在不同的浏览器实例中打开。
创建广播频道
在发送消息的页面中,首先需要创建一个 Broadcast Channel,可以使用 new BroadcastChannel(channelName)
构造函数。
channelName 是频道的名称,所有使用相同名称的页面都能收到彼此的消息。
// 创建广播
const channelS = new BroadcastChannel("myChannel");
const count = ref(0);
function onSend() {
count.value++;
let msg = { message: "BroadCast Channel 消息:" + count.value };
// 通过广播频道发送消息
channelS.postMessage(msg);
state.sData.push(msg);
}
接收消息
在接收消息的页面中,同样需要创建一个同名的广播频道,并通过 addEventListener 监听消息事件。
const channelA = new BroadcastChannel("myChannel");
channelA.addEventListener("message", function (e) {
state.aData.push(e.data);
});
关闭频道
当不再需要广播频道时,最好在页面关闭或不再需要通信的时候移除监听器,以释放资源。
切记最好在页面关闭或不再需要通信的时候移除监听器,否则重新进入页面时会再次创建一个监听器,上次创建的监听器还存在,这样的话每次进入页面都会创建一个。
onUnmounted(() => {
channelA.close();
// 移除事件监听器
channel.removeEventListener('message', handleReceivedMessage());
});
注意:使用 Broadcast Channel API 的两个页面必须在同一协议(http 或 https)下,否则无法进行通信。此外,同一页面打开多个标签页也可以通过 Broadcast Channel 进行通信。
示例代码
LocalStorage storage 事件和 Broadcast Channel API 示例
BroadCastChannel.vue
<script setup lang="ts">
import { onUnmounted, onMounted, reactive, ref } from "vue";
const state = reactive({
sData: [], // 发送消息列表
aData: [], // 接受消息列表
});
// 发送消息
const channelS = new BroadcastChannel("myChannel");
const count = ref(0);
function onSend() {
count.value++;
let msg = { message: "BroadCast Channel 消息:" + count.value };
channelS.postMessage(msg);
state.sData.push(msg);
}
// 接受消息
const channelA = new BroadcastChannel("myChannel");
channelA.addEventListener("message", function (e) {
state.aData.push(e.data);
});
onMounted(() => {
// 监听 LocalStorage 变化消息
window.addEventListener("storage", function (e) {
if (e.key === "message") {
state.aData.push(JSON.parse(e.newValue));
}
});
});
onUnmounted(() => {
channelS.close();
channelA.close();
});
</script>
<template>
<div class="container">
<h1>BroadCast Channel 通信</h1>
<el-button @click="onSend" type="primary">发送</el-button>
<div style="padding: 20px">已发送 {{ count }} 次消息。</div>
<el-row :gutter="20">
<el-col :span="12" style="border: 1px solid #ccc">
<div style="padding: 20px; font-weight: 700">发送消息列表</div>
<el-alert v-for="item in state.sData" :key="item" :title="item.message" type="warning" :closable="false" style="margin: 10px 0"></el-alert>
</el-col>
<el-col :span="12" style="border: 1px solid #ccc">
<div style="padding: 20px; font-weight: 700">接受消息列表</div>
<el-alert v-for="item in state.aData" :key="item" :title="item.message" type="success" :closable="false" style="margin: 10px 0"></el-alert>
</el-col>
</el-row>
</div>
</template>
<style lang="scss" scoped>
.container {
text-align: center;
}
</style>
LocalStorage.vue
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
const state = reactive({
sData: [], // 发送消息列表
aData: [], // 接受消息列表
});
// 发送消息
const count = ref(0);
function onSend() {
count.value++;
let msg = { message: "LocalStorage 消息:" + count.value };
localStorage.setItem("message", JSON.stringify(msg));
state.sData.push(msg);
}
</script>
<template>
<div class="container">
<h1>LocalStorage 通信</h1>
<el-button @click="onSend" type="primary">发送</el-button>
<div style="padding: 20px">已发送 {{ count }} 次消息。</div>
<el-row :gutter="20">
<el-col :span="12" style="border: 1px solid #ccc">
<div style="padding: 20px; font-weight: 700">发送消息列表</div>
<el-alert
v-for="item in state.sData"
:key="item"
:title="item.message"
type="warning"
:closable="false"
style="margin: 10px 0"
></el-alert>
</el-col>
<el-col :span="12" style="border: 1px solid #ccc">
<div style="padding: 20px; font-weight: 700">接受消息列表</div>
<el-alert
v-for="item in state.aData"
:key="item"
:title="item.message"
type="success"
:closable="false"
style="margin: 10px 0"
></el-alert>
</el-col>
</el-row>
</div>
</template>
<style lang="scss" scoped>
.container {
text-align: center;
}
</style>