React 与 Vue 跨框架状态同步:从“状态孤岛”到 StateBus 实践
React 与 Vue 跨框架状态同步从“状态孤岛”到 StateBus 实践痛点当 React 遇上 Vue微前端落地后最先头疼的往往不是路由或样式而是状态。想象一个场景主应用是 React子应用是 Vue。用户在 Vue 里改了下主题切回 React 主应用发现主题还是旧的。两个框架各自维护独立的状态树互不相通这就是典型的“状态孤岛”。但这不仅仅是简单的“发布-订阅”能解决的。React 的批量更新机制、Vue 的响应式调度两者对同一数据的渲染策略不同直接同步很容易产生时序问题。如果直接把 Redux 或 Pinia 的模式硬搬过去在跨框架场景下很容易遇到意想不到的竞态条件。核心思路引入框架无关的中间层问题的关键在于两个框架的更新调度机制完全不同React 18引入自动批量更新Automatic Batching多次setState会合并为一次渲染。Vue 3基于 Proxy 的依赖追踪在nextTick中批量刷新。要让它们共享同一份数据必须引入一个框架无关的中间层。StateBus发布订阅中心StateBus 是整个架构的核心。它不依赖任何框架只负责维护一份状态快照。所有更新通过队列顺序执行确保跨框架的更新顺序一致。React Hook 和 Vue Composable 分别作为适配层将框架的更新机制与 StateBus 对接。// state-bus.js — 跨框架状态同步核心 class StateBus { constructor(initialState {}) { // 状态快照只读引用每次更新生成新对象 this._snapshot Object.freeze({ ...initialState }); // 订阅者注册表 this._subscribers new Map(); // 更新队列防止同步更新导致的级联渲染 this._updateQueue []; this._isFlushing false; this._idCounter 0; } getSnapshot() { return this._snapshot; } dispatch(updater) { const partial typeof updater function ? updater(this._snapshot) : updater; // 生成新的不可变快照 this._snapshot Object.freeze({ ...this._snapshot, ...partial }); this._enqueueNotify(); } subscribe(callback) { const id this._idCounter; this._subscribers.set(id, callback); return () this._subscribers.delete(id); } _enqueueNotify() { if (this._isFlushing) return; this._isFlushing true; const prevSnapshot this._snapshot; // 使用微任务调度与框架的批量更新对齐 Promise.resolve().then(() { this._isFlushing false; const currentSnapshot this._snapshot; for (const [id, callback] of this._subscribers) { try { callback(currentSnapshot, prevSnapshot); } catch (err) { console.error([StateBus] 订阅者 ${id} 回调异常:, err); } } }); } } // 导出单例工厂 let instance null; export function createStateBus(initialState) { if (!instance) { instance new StateBus(initialState); } return instance; }React 适配层React 侧直接使用useSyncExternalStore这是 React 18 专门为外部状态管理设计的 Hook能保证并发模式下的状态一致性避免 tearing 问题。// use-shared-state.react.js import { useSyncExternalStore } from react; export function useSharedState(stateBus, selector) { const snapshot useSyncExternalStore( (callback) stateBus.subscribe(callback), () { const state stateBus.getSnapshot(); return selector ? selector(state) : state; } ); const dispatch (updater) stateBus.dispatch(updater); return [snapshot, dispatch]; }Vue 适配层Vue 侧用ref持有本地响应式副本订阅 StateBus 变更后同步到本地。这里用JSON.stringify做浅比较避免无意义的响应式触发。// use-shared-state.vue.js import { ref, watch, onUnmounted } from vue; export function useSharedState(stateBus, selector) { const localState ref( selector ? selector(stateBus.getSnapshot()) : stateBus.getSnapshot() ); const unsubscribe stateBus.subscribe((newSnapshot) { const selected selector ? selector(newSnapshot) : newSnapshot; if (JSON.stringify(localState.value) ! JSON.stringify(selected)) { localState.value selected; } }); onUnmounted(() unsubscribe()); const dispatch (updater) stateBus.dispatch(updater); return [localState, dispatch]; }实战接入主应用React初始化import { createStateBus } from ./state-bus; const bus createStateBus({ theme: dark, locale: zh-CN, userPreferences: {} }); // 挂载到 window供子应用访问 window.__SHARED_STATE_BUS__ bus; function ThemeToggle() { const [theme, dispatch] useSharedState(bus, (s) s.theme); return ( button onClick{() dispatch({ theme: theme dark ? light : dark })} 当前主题{theme} /button ); }子应用Vue接入import { useSharedState } from ./use-shared-state.vue; const bus window.__SHARED_STATE_BUS__; export default { setup() { const [theme, dispatch] useSharedState(bus, (s) s.theme); watch(theme, (newTheme) { document.documentElement.setAttribute(data-theme, newTheme); }); return { theme, dispatch }; } };实战中的坑与边界这套方案在生产环境用下来有几个点需要特别注意1. 一致性模型的妥协StateBus 采用的是“最终一致性”。微任务调度意味着在同一事件循环内React 和 Vue 对同一状态的读取可能短暂不一致。主题切换这种低频操作没问题但如果是实时协作编辑器的光标位置这种延迟会导致视觉抖动。2. 序列化成本Vue 适配层用JSON.stringify做浅比较当状态对象较大或包含函数、Symbol 时会出错。生产环境建议替换为浅比较函数或者用Object.is逐键对比。3. 单例挂载的脆弱性通过window.__SHARED_STATE_BUS__传递实例依赖全局命名空间存在被覆盖的风险。更稳妥的做法是通过微前端框架如 qiankun的 props 传递或者用 CustomEvent 做桥接。4. 性能瓶颈当子应用超过 5 个、状态节点超过 50 个时单一 StateBus 会成为瓶颈。这时候应该按业务域拆分成多个 Bus每个 Bus 只负责一个子域的状态。5. 禁用场景高频实时数据如股票行情、游戏帧同步不适合这个方案。这类场景应该用 SharedArrayBuffer 或 WebSocket 直连绕过框架状态层。总结跨框架微前端的状态同步核心就是引入一个框架无关的中间层。通过发布-订阅和微任务调度React 用useSyncExternalStore保证并发一致性Vue 用ref加浅比较避免冗余渲染。这套方案适合主题、偏好、权限这类低频共享状态。但如果状态规模增长记得按业务域拆分 StateBus别让它变成单一中心节点。

相关新闻