多窗口数据实时同步常规方案举例

要实现多窗口(或多标签页)数据实时同步(无需刷新),核心是利用 浏览器跨窗口通信能力 结合 状态管理,让一个窗口的数据变化实时通知到其他窗口。以下是具体实现方案,按「通用性 + 复杂度」排序:

一、核心原理:浏览器跨窗口通信方案

浏览器提供了 3 种主流的跨窗口通信方式,均支持同源(同协议、同域名、同端口)下的窗口数据同步:

方案 核心 API 优点 缺点 适用场景
Broadcast Channel new BroadcastChannel() 极简(一行代码发送 / 接收)、性能好 不支持 IE(移动端 / 现代浏览器均支持) 同源多窗口 / 多标签页同步
LocalStorage 监听 window.addEventListener('storage') 兼容性极强(支持所有浏览器) 依赖存储事件(需修改 localStorage) 需兼容旧浏览器的场景
SharedWorker new SharedWorker() 支持复杂逻辑(如实时计算、长连接) 实现稍复杂(需单独写 Worker 文件) 多窗口需共享复杂业务逻辑

二、具体实现方案(推荐优先用 Broadcast Channel)

以「阅读数量同步」为例,3 种方案的完整代码如下,可直接复制使用:

方案 1:Broadcast Channel(最简单,推荐)

原理:创建一个「广播频道」,所有窗口加入该频道;当一个窗口的阅读数变化时,向频道发送消息,其他窗口接收消息后更新数据。

1. 封装通信工具(可复用)

创建 src/utils/channel.js,统一管理广播频道:

javascript 复制代码
// 1. 创建全局唯一的广播频道(频道名自定义,需确保所有窗口一致)
const channel = new BroadcastChannel('read-count-sync');

// 2. 发送消息(数据变化时调用,如阅读数+1)
export function sendReadCount(count) {
  channel.postMessage({
    type: 'READ_COUNT_UPDATE', // 消息类型(区分不同同步内容)
    data: count // 要同步的阅读数
  });
}

// 3. 接收消息(其他窗口发送消息时触发,用于更新本地数据)
export function onReadCountUpdate(callback) {
  // 监听频道消息
  channel.onmessage = (e) => {
    // 只处理阅读数更新的消息
    if (e.data.type === 'READ_COUNT_UPDATE') {
      callback(e.data.data); // 把最新的阅读数传给回调函数
    }
  };

  // 销毁频道(避免内存泄漏,在组件卸载时调用)
  return () => {
    channel.close();
  };
}
2. 窗口 A(阅读操作窗口):发送数据变化

当用户在窗口 A 阅读时,更新本地阅读数,并通过广播通知其他窗口:

xml 复制代码
<template>
  <div class="read-page">
    <h2>当前阅读数:{{ readCount }}</h2>
    <button @click="handleRead">点击阅读(+1)</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { sendReadCount } from '@/utils/channel';

// 本地阅读数状态(初始值可从接口/缓存获取)
const readCount = ref(0);

// 阅读操作:更新本地数据 + 广播通知其他窗口
const handleRead = () => {
  readCount.value += 1;
  // 发送最新的阅读数到广播频道
  sendReadCount(readCount.value);
};
</script>
3. 窗口 B(需同步的窗口):接收数据并更新

窗口 B 初始化时监听广播,收到消息后更新本地弹框的阅读数:

xml 复制代码
<template>
  <!-- 弹框组件:展示阅读数 -->
  <van-dialog v-model:show="dialogShow" title="阅读统计">
    <p>当前阅读数:{{ readCount }}</p>
  </van-dialog>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';
import { onReadCountUpdate } from '@/utils/channel';

// 弹框显示状态(假设默认打开)
const dialogShow = ref(true);
// 本地阅读数状态(初始值可从接口/缓存获取)
const readCount = ref(0);

// 监听阅读数更新:收到其他窗口的消息后,更新本地数据
const destroyChannel = onReadCountUpdate((newCount) => {
  readCount.value = newCount; // 无需刷新,实时更新弹框内容
});

// 组件卸载时销毁广播频道(避免内存泄漏)
onUnmounted(() => {
  destroyChannel();
});
</script>

方案 2:LocalStorage 监听(兼容所有浏览器)

原理 :LocalStorage 是同源窗口共享的存储;当一个窗口修改 LocalStorage 时,其他窗口会触发 storage 事件,通过监听该事件同步数据。

1. 封装工具(src/utils/storageSync.js
javascript 复制代码
// 存储的 key(自定义,需所有窗口一致)
const READ_COUNT_KEY = 'read-count';

// 1. 更新阅读数并触发 storage 事件
export function updateReadCount(count) {
  // 修改 LocalStorage(会触发其他窗口的 storage 事件)
  localStorage.setItem(READ_COUNT_KEY, JSON.stringify({
    value: count,
    timestamp: Date.now() // 避免旧数据覆盖(可选)
  }));
}

// 2. 监听阅读数变化
export function onReadCountChange(callback) {
  // 监听 storage 事件
  const handleStorage = (e) => {
    // 只处理阅读数相关的存储变化
    if (e.key === READ_COUNT_KEY) {
      const { value } = JSON.parse(e.newValue); // 最新的阅读数
      callback(value);
    }
  };

  window.addEventListener('storage', handleStorage);

  // 移除监听(组件卸载时调用)
  return () => {
    window.removeEventListener('storage', handleStorage);
  };
}

// 3. 获取当前阅读数(初始化时用)
export function getCurrentReadCount() {
  const data = localStorage.getItem(READ_COUNT_KEY);
  return data ? JSON.parse(data).value : 0;
}
2. 窗口 A(阅读操作)
xml 复制代码
<script setup>
import { ref } from 'vue';
import { updateReadCount, getCurrentReadCount } from '@/utils/storageSync';

// 初始化:从 LocalStorage 获取当前阅读数
const readCount = ref(getCurrentReadCount());

const handleRead = () => {
  readCount.value += 1;
  // 更新 LocalStorage,触发其他窗口同步
  updateReadCount(readCount.value);
};
</script>
3. 窗口 B(同步窗口)
xml 复制代码
<script setup>
import { ref, onUnmounted } from 'vue';
import { onReadCountChange, getCurrentReadCount } from '@/utils/storageSync';

const dialogShow = ref(true);
// 初始化:从 LocalStorage 获取初始值
const readCount = ref(getCurrentReadCount());

// 监听 storage 事件,同步数据
const removeListener = onReadCountChange((newCount) => {
  readCount.value = newCount; // 实时更新弹框
});

// 组件卸载时移除监听
onUnmounted(() => {
  removeListener();
});
</script>

注意storage 事件 只在其他窗口修改 LocalStorage 时触发,当前窗口修改不会触发(需手动更新本地数据,如上代码已处理)。

方案 3:SharedWorker(复杂场景用)

原理:创建一个共享的 Worker 线程,所有窗口通过 Worker 通信;Worker 作为「中间件」转发数据,支持更复杂的逻辑(如统计所有窗口的阅读总数)。

1. 创建 Worker 文件(src/workers/readSync.worker.js
ini 复制代码
// Worker 线程:管理所有窗口的连接和消息转发
const connections = new Set(); // 存储所有窗口的连接

// 监听窗口连接
self.onconnect = (e) => {
  const port = e.ports[0]; // 获取当前窗口的通信端口
  connections.add(port); // 加入连接集合

  // 监听窗口发送的消息
  port.onmessage = (msg) => {
    if (msg.data.type === 'READ_COUNT_UPDATE') {
      // 转发消息到所有其他窗口
      connections.forEach(conn => {
        if (conn !== port) { // 不发给自己
          conn.postMessage(msg.data);
        }
      });
    }
  };

  // 窗口断开连接时移除
  port.onmessageerror = () => {
    connections.delete(port);
  };
  port.onerror = () => {
    connections.delete(port);
  };
};
2. 封装工具(src/utils/workerSync.js
javascript 复制代码
// 1. 创建 SharedWorker 实例
const worker = new SharedWorker(new URL('@/workers/readSync.worker.js', import.meta.url));
const port = worker.port; // 获取通信端口
port.start(); // 启动端口通信

// 2. 发送阅读数更新
export function sendReadCount(count) {
  port.postMessage({
    type: 'READ_COUNT_UPDATE',
    data: count
  });
}

// 3. 接收其他窗口的更新
export function onReadCountUpdate(callback) {
  port.onmessage = (e) => {
    if (e.data.type === 'READ_COUNT_UPDATE') {
      callback(e.data.data);
    }
  };

  // 销毁连接(组件卸载时调用)
  return () => {
    port.close();
  };
}
3. 窗口 A 和 B 的使用方式

与「方案 1(Broadcast Channel)」完全一致,只需替换导入的工具函数即可:

javascript 复制代码
// 窗口 A 发送
import { sendReadCount } from '@/utils/workerSync';

// 窗口 B 接收
import { onReadCountUpdate } from '@/utils/workerSync';

三、注意

  1. 同源限制 :以上 3 种方案均只支持 同源窗口 (同协议、同域名、同端口);若跨域名(如 a.comb.com),需用「跨域通信方案」(如 postMessage + iframe 桥接),但你的场景是同一项目的两个窗口,无需考虑跨域。
  2. 初始值同步:新打开的窗口需先从「数据源」(如接口、LocalStorage)获取初始阅读数,再监听后续变化;避免初始值为 0,导致同步前显示错误。
  3. 内存泄漏 :所有监听事件(如 onmessagestorage)需在组件卸载时销毁(如关闭广播频道、移除 storage 监听、关闭 Worker 端口),避免浏览器内存占用过高。
  4. 数据一致性:若多个窗口同时修改阅读数(如窗口 A 和 B 同时点击阅读),需确保数据不会冲突;可通过「时间戳」或「版本号」判断数据新旧,或让 Worker 统一处理计数(方案 3 适合)。
相关推荐
小p8 小时前
react学习2:react中常用的hooks
前端·react.js
南清的coding日记8 小时前
Java 程序员的 Vue 指南 - Vue 万字速览(01)
java·开发语言·前端·javascript·vue.js·css3·html5
Xiaouuuuua8 小时前
2026年计算机毕业设计项目合集
前端·vue.js·课程设计
IT_陈寒8 小时前
React 18并发模式实战:3个优化技巧让你的应用性能提升50%
前端·人工智能·后端
用户761736354018 小时前
CSS重点知识-样式计算
前端
yoyoma8 小时前
object 、 map 、weakmap区别
前端·javascript
shawn_yang8 小时前
实现公历和农历日期选择组件(用于选择出生日期)
vue.js·vant
shyshi8 小时前
vercel 部署 node 服务和解决 vercel 不可访问的问题
前端·javascript
.生产的驴8 小时前
React 模块化Axios封装请求 统一响应格式 请求统一处理
前端·javascript·react.js·前端框架·json·ecmascript·html5