基于Web Component的React与Vue跨栈系统融合实践
基于Web Component的React与Vue跨栈系统融合实践
一、背景与需求
最近一直会有一些这样的需求, 两套完全独立的前端系统,分别基于React和Vue框架开发,用户体系及鉴权体系独立,本次测试将尝试把Vue系统嵌入React中,实现核心交互逻辑:点击切换至React系统时,侧边栏(Aside)渲染React菜单,内容区(Content)加载React组件;切换至Vue系统时,侧边栏与内容区同步渲染Vue对应的菜单及组件,形成视觉与功能统一的集成体验,基础UI如下图:
![]()
二、技术环境
-
Vue技术栈:Vue3 + Vite.js + UnoCss + TypeScript (Vue项目用的是开源的)
-
React技术栈:React17 + Webpack + Sass + TypeScript (React项目是自有的)
-
后端及部署:Spring Boot + JAVA17 + Docker + MySQL + Redis (Vue项目后台)
三、方案选型
目前微前端领域已有qiankun.js、MicroApp等成熟方案,但也又一定的局限性,本次实践旨在探索更轻量化的浏览器原生方案——Web Component。作为W3C制定的浏览器原生组件化标准,Web Component具备跨框架UI复用与封装能力,无需依赖第三方框架,可天然实现不同技术栈的融合。
四、工程改造实现
4.1 Vue工程改造(Web Component打包)
核心目标是将Vue项目打包为可被React调用的Web Component自定义元素,需新增专属入口文件并配置打包规则。
4.1.1 新增Web Component入口文件
创建src/web-component-entry.ts作为打包入口,封装Vue应用为自定义元素,实现组件的挂载、卸载与属性监听,以下是伪代码:
// src/web-component-entry.ts
import App from "./App.vue";
import { createApp, h } from "vue";
class VueWebComponentElement extends HTMLElement {
private _app: any = null;
private _reactToken: string = "";
// 定义需要监听的属性
static get observedAttributes() {
return ["mode"];
}
constructor() {
super();
// 监听来自React的事件
this.addEventListener("app-changed", (e: CustomEvent) => {
const { token } = e.detail;
this._reactToken = token;
});
}
async connectedCallback() {
if (this._app) return;
// 创建挂载容器并设置样式
const rootNode = document.createElement("div");
rootNode.setAttribute("id", "app-vue");
rootNode.style.height = "100%";
this.appendChild(rootNode);
// 获取属性并初始化Vue应用
const mode = this.getAttribute("mode") || "full";
const app = createApp({
render() {
return h(App, { mode });
},
});
// 比如挂载Vue生态依赖(权限、指令、全局组件、Store、Router等)
app.mount(rootNode);
this._app = app;
}
// 属性变化回调
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
// 可根据属性变化执行对应逻辑(如样式切换、数据更新)
}
// 组件卸载回调
disconnectedCallback() {
if (this._app) {
this._app.unmount();
delete this._app;
}
}
}
// 定义自定义元素(避免重复定义)
if (!customElements.get("wc-pvue")) {
customElements.define("wc-pvue", VueWebComponentElement);
}
export default VueWebComponentElement;
4.1.2 Vite打包配置调整
在vite.config.ts中新增Web Component打包模式,指定输出格式、入口文件及资源命名规则:
// vite.config.ts部分配置
import { defineConfig, loadEnv, resolve } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const isWebComponent = env.VITE_BUILD_MODE === "webcomponent";
return {
plugins: [vue()],
build: {
minify: "terser",
// 区分Web Component打包目录
outDir:
env.VITE_OUT_DIR && isWebComponent
? `${env.VITE_OUT_DIR}/web-component`
: env.VITE_OUT_DIR || "dist",
sourcemap: env.VITE_SOURCEMAP === "true" ? "inline" : false,
terserOptions: {
compress: {
drop_debugger: env.VITE_DROP_DEBUGGER === "true",
drop_console: env.VITE_DROP_CONSOLE === "true",
},
},
// Web Component专属打包配置
...(isWebComponent
? {
lib: {
entry: resolve(__dirname, "src/web-component-entry.ts"),
name: "PVue",
fileName: "pvue",
formats: ["umd"], // 输出UMD格式,兼容浏览器环境
},
rollupOptions: {
output: {
entryFileNames: "pvue.js",
assetFileNames: "pvue.[ext]",
},
},
}
: {}),
},
};
});
注:为简化测试,当前配置未分离Vue运行时依赖,导致最终UMD文件体积偏大。若需优化体积,可通过external配置排除Vue核心依赖,但需在React项目中同步引入对应依赖,确保Vue应用运行环境完整。
4.2 React工程改造(集成Web Component)
React端需通过布局组件控制系统切换逻辑,同时引入Vue打包后的资源文件。
4.2.1 布局组件改造
在layout.tsx中通过状态控制渲染逻辑,切换至Vue系统时加载自定义元素<wc-pvue />:
import React, { useState } from "react";
import { Layout } from "antd"; // 假设使用Ant Design布局组件
import SiderMenu from "./SiderMenu";
import Header from "./Header";
import styles from "./layout.module.sass";
const AppLayout = ({ children }: { children: React.ReactNode }) => {
const [app, setApp] = useState<"react" | "vue">("react");
// 系统切换回调
const onAppChanged = (targetApp: "react" | "vue") => {
setApp(targetApp);
// 延迟发送事件,确保Vue组件已渲染
setTimeout(() => {
const wcEl = document.querySelector("wc-pvue");
wcEl?.dispatchEvent(
new CustomEvent("app-changed", {
detail: {
token: (cache.getCache("accessInfo", "session") as any)
?.accessToken,
},
bubbles: true,
composed: true, // 允许事件穿透Shadow DOM
}),
);
}, 500);
};
return (
<Layout className={styles["app-layout-wrapper"]}>
<Header onAppChanged={onAppChanged} />
{app === "react" ? (
<Layout className={styles["app-content-wrapper"]}>
<SiderMenu />
<Layout>{children}</Layout>
</Layout>
) : (
// 加载Vue对应的Web Component
<wc-pvue />
)}
</Layout>
);
};
export default AppLayout;
4.2.2 引入Vue资源
在React项目的index.html中引入Vue打包后的CSS与JS文件,确保自定义元素可正常渲染:
<!-- 引入Vue Web Component样式 -->
<link rel="stylesheet" href="vue/pvue.css" /<!-- 引入Vue Web Component脚本 -->
至此,基础嵌入功能实现完成,可通过切换菜单验证两侧系统的渲染效果。
五、关键技术点突破
5.1 样式隔离与覆盖
Web Component天然支持Shadow DOM,可构建独立DOM树实现样式隔离,避免与React主系统样式冲突;Vue端也可通过Scoped CSS限定样式作用域。但实际业务中常需覆盖子系统样式,结合本次Vue项目使用UnoCSS及CSS变量的特性,采用变量覆盖方案实现样式定制:
wc-pvue {
height: 100%;
/* 覆盖Vue项目内部CSS变量 */
--app-footer-height: 0px;
--tags-view-height: 0px;
--top-tool-height: 0px;
/* 隐藏Vue项目中不需要的元素 */
#v-tool-header,
#v-tags-view {
display: none;
}
}
样式覆盖需结合项目实际场景调整:若无法通过CSS变量或选择器覆盖,需修改Vue项目源码;若涉及主题切换等动态需求,可通过自定义元素属性传递状态,在Vue端监听属性变化同步更新样式。
5.2 跨框架消息通讯
UI层嵌入仅完成视觉整合,跨框架逻辑协同的核心在于消息通讯。常用方案包括全局状态共享(挂载至window)、属性传递、事件驱动等,本次实践采用浏览器原生CustomEvent实现解耦式通讯。
前文实现了React向Vue发送事件传递Token,但通过setTimeout规避渲染时机问题的方案存在不稳定性。更优实践为Vue主动发起通讯:在Vue组件的connectedCallback生命周期中发送就绪事件,React监听该事件后再传递数据,确保渲染与通讯时序一致:
// Vue端:web-component-entry.ts 中修改connectedCallback
async connectedCallback() {
// 省略原有挂载逻辑...
// 组件挂载完成后通知React
this.dispatchEvent(
new CustomEvent('vue-ready', {
bubbles: true,
composed: true
})
)
}
// React端:layout.tsx 中监听事件
useEffect(() => {
const handleVueReady = () => {
const wcEl = document.querySelector('wc-pvue')
wcEl?.dispatchEvent(
new CustomEvent('app-changed', {
detail: { token: (cache.getCache('accessInfo', 'session') as any)?.accessToken },
bubbles: true,
composed: true
})
)
}
document.addEventListener('vue-ready', handleVueReady)
return () => document.removeEventListener('vue-ready', handleVueReady)
}, [])
六、实践总结与待解决问题
基于Web Component可实现React与Vue跨栈系统的基础融合,通过自定义元素封装、原生事件通讯、CSS变量覆盖等手段,满足核心交互与样式适配需求。但本次实践仍存在诸多待优化点:
-
路由兼容性:React采用BrowserRouter(HTML5 History模式),Vue采用HashRouter,两者路由规则冲突,且页面切换时HTML标题同步、路由守卫协同等问题未解决。可通过统一路由模式(如均采用History模式)、主应用接管路由分发实现兼容。
-
统一认证体系:两套系统原有独立登录权限机制,目前仅实现Token传递,未完成身份态同步、权限统一校验等功能,需设计跨系统认证中心或共享令牌机制。
-
第三方系统改造限制:本次实践基于可自由修改的开源Vue项目,若需嵌入第三方不可控Vue系统,无法进行源码改造,需探索无侵入式封装方案。
相较于qiankun等成熟微前端框架,Web Component也是一种更轻量化的选择方案, 具体实践依然要根据具体的项目情况来选择和评估。当然,后续抽空还会分享一种基于类似门户系统的iframe融合方案,但不会在浏览器打开新页签,大家还有哪些方案可以分享呢,欢迎留言讨论!