export本身不提供跨标签页同步能力,它只是模块语法;真正的跨页同步需封装BroadcastChannel与状态管理(如Zustand),导出具备监听、广播、响应式更新的“智能状态对象”,并内置同源校验、防重、单例等关键逻辑。

export 本身不提供跨标签页同步能力,它只是 JavaScript 模块系统的语法,用于声明模块对外暴露的变量、函数或类。所谓“具备跨标签页同步感知的全局状态模块”,其实是将状态管理逻辑(如 Zustand store 或自定义 Hook)与 BroadcastChannel 等通信机制封装在一起,并通过 export 导出这个**可复用、可监听、可响应变化**的状态接口。
导出一个带同步能力的状态模块
核心思路是:不导出原始数据,而是导出一个封装了本地状态 + 跨页通信监听/广播逻辑的“智能状态对象”。例如:
- 用
createStore(Zustand)或ref(Vue)管理本地状态 - 在模块初始化时创建
BroadcastChannel实例,监听同源其他标签页发来的状态变更消息 - 提供
update方法,既更新本地状态,也向频道广播变更 - 用
export default或export const useSharedState暴露这个能力
关键实现细节不能漏
导出前必须确保以下几点已内建到模块中:
-
同源校验与降级处理:检查
window.BroadcastChannel是否可用,不可用时自动 fallback 到 localStorage + storage 事件(仅作兜底) - 消息防重与顺序保障:每条广播消息附带时间戳或唯一 ID,接收端跳过旧消息,避免状态回滚
-
频道单例管理:整个模块只创建一次
new BroadcastChannel('auth-state'),避免重复监听或内存泄漏 -
状态变更的语义化:广播内容不是裸数据,而是结构化动作,如
{ type: 'LOGOUT', timestamp: 1746694200123 }
典型导出示例(Zustand + TypeScript)
以下是一个可直接 import 使用的模块写法:
src/stores/useAuthSync.tsimport { create } from 'zustand';
import { useEffect } from 'react';
const channel = typeof window !== 'undefined' && 'BroadcastChannel' in window
? new BroadcastChannel('auth-sync')
: null;
export const useAuthSync = create<AuthState & AuthActions>((set, get) => ({
token: localStorage.getItem('auth-token') || null,
isLoggedIn: !!localStorage.getItem('auth-token'),
login: (token) => {
localStorage.setItem('auth-token', token);
set({ token, isLoggedIn: true });
channel?.postMessage({ type: 'LOGIN', token, timestamp: Date.now() });
},
logout: () => {
localStorage.removeItem('auth-token');
set({ token: null, isLoggedIn: false });
channel?.postMessage({ type: 'LOGOUT', timestamp: Date.now() });
}
}));
// 全局监听同步事件
if (channel) {
channel.onmessage = (event) => {
if (event.data.type === 'LOGIN') {
useAuthSync.setState({ token: event.data.token, isLoggedIn: true });
}
if (event.data.type === 'LOGOUT') {
useAuthSync.setState({ token: null, isLoggedIn: false });
}
};
}
其他页面只需 import { useAuthSync } from './stores/useAuthSync',即可获得自动同步的登录状态。
为什么不能只 export 一个普通对象?
因为普通对象不具备响应性与通信能力:
- 导出
export const state = { token: null }—— 修改它不会触发组件更新,也无法通知其他标签页 - 导出
export function getState()—— 只是取值快照,没有订阅机制 - 真正需要的是 导出一个“活”的状态接口:它内部持有引用、监听通道、响应变化、支持派生逻辑

















