You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 07:18:22