Next.js 14:如何实现跨浏览器标签页共享的Singleton类?
问题:Next.js 14中实现跨标签页共享且持久化的Singleton类
我正在开发Next.js 14应用,需要实现一个可跨多个浏览器标签页共享状态的Singleton类,要求:
- 页面跳转、刷新、关闭重开标签页时状态保留
- 避免每次刷新/新开标签页重新获取数据
- 标签页间状态同步
- 不能使用浏览器本地存储(如localStorage/sessionStorage),需保存在共享内存位置
我尝试用基础JS类加Object.freeze实现,但每次刷新或新开标签页都会重新初始化实例,代码如下:
class Singleton { value = []; constructor() {} addPost = (data) => { this.value.push(data); }; } const instance = new Singleton(); Object.freeze(instance); export default instance;
根布局代码:
import singleton from "@/class/Singleton"; import { Inter } from "next/font/google"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children }) { console.log("singleton: ", singleton); return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ); }
解决方案:基于SharedWorker实现跨标签页共享内存Singleton
浏览器每个标签页都是独立的JS执行上下文,普通模块导出的Singleton只能在单个标签页内生效。要实现跨标签页共享内存状态,SharedWorker是合适的选择——它运行在独立的线程中,可被同源的多个标签页共享,状态保存在其内存中。
1. 创建SharedWorker脚本
在public目录下创建shared-worker.js(Next.js可直接访问public下的资源):
// public/shared-worker.js let sharedState = { value: [] }; const connections = new Set(); // 处理来自标签页的连接 self.onconnect = (e) => { const port = e.ports[0]; connections.add(port); // 首次连接时发送当前共享状态 port.postMessage({ type: 'INIT_STATE', payload: sharedState }); port.onmessage = (msg) => { switch (msg.data.type) { case 'ADD_POST': sharedState.value.push(msg.data.payload); // 同步所有连接的标签页 broadcastState(); break; case 'GET_STATE': port.postMessage({ type: 'STATE_UPDATED', payload: sharedState }); break; } }; port.onclose = () => { connections.delete(port); }; }; // 向所有标签页广播最新状态 function broadcastState() { connections.forEach(port => { if (port.readyState === 'open') { port.postMessage({ type: 'STATE_UPDATED', payload: sharedState }); } }); }
2. 封装Singleton类,与SharedWorker通信
创建app/class/SharedSingleton.js:
// app/class/SharedSingleton.js class SharedSingleton { constructor() { this.state = { value: [] }; this.worker = new SharedWorker('/shared-worker.js'); this.port = this.worker.port; // 监听Worker发来的状态更新 this.port.onmessage = (e) => { switch (e.data.type) { case 'INIT_STATE': case 'STATE_UPDATED': this.state = e.data.payload; // 触发状态更新回调(用于React状态同步) this.onStateChange?.(); break; } }; this.port.start(); } // 添加状态更新订阅 subscribe(callback) { this.onStateChange = callback; } // 添加数据到共享状态 addPost(data) { this.port.postMessage({ type: 'ADD_POST', payload: data }); } // 获取当前共享状态(返回副本避免直接修改) getState() { return { ...this.state }; } } // 在客户端创建唯一实例(仅在浏览器环境可用) let instance; export const getSharedSingleton = () => { if (typeof window === 'undefined') { throw new Error('SharedSingleton只能在客户端环境使用'); } if (!instance) { instance = new SharedSingleton(); } return instance; };
3. 在Next.js客户端组件中使用
SharedWorker是浏览器API,必须在客户端组件中使用(添加'use client'指令):
示例客户端组件app/components/PostManager.js:
'use client'; import { useEffect, useState } from 'react'; import { getSharedSingleton } from '../class/SharedSingleton'; export default function PostManager() { const [posts, setPosts] = useState([]); const singleton = getSharedSingleton(); useEffect(() => { // 初始化状态 setPosts(singleton.getState().value); // 订阅状态更新 singleton.subscribe(() => { setPosts(singleton.getState().value); }); }, []); const handleAddPost = () => { singleton.addPost(`Post ${Date.now()}`); }; return ( <div> <button onClick={handleAddPost}>添加帖子</button> <ul> {posts.map((post, idx) => ( <li key={idx}>{post}</li> ))} </ul> </div> ); }
关键说明
- SharedWorker特性:同源下所有标签页共享同一个Worker实例,状态保存在Worker的内存中;只要浏览器不关闭,Worker会持续运行(所有连接的标签页关闭后,Worker会被销毁)。
- 状态同步:通过广播机制,当一个标签页修改状态时,所有其他标签页都会收到更新通知。
- Next.js适配:必须在客户端组件中使用,服务端环境无法访问浏览器API。
内容的提问来源于stack exchange,提问作者GabrielCard
相关产品推荐
相关产品推荐

