基于Web Component的React与Vue跨栈系统融合实践
最近一直会有一些这样的需求, 两套完全独立的前端系统,分别基于React和Vue框架开发,用户体系及鉴权体系独立,本次测试将尝试把Vue系统嵌入React中,实现核心交互逻辑:点击切换至React系统时,侧边栏(Aside)渲染React菜单,内容区(Content)加载React组件;切换至Vue系统时,侧边栏与内容区同步渲染Vue对应的菜单及组件,形成视觉与功能统一的集成体验,基础UI如下图: 目前微前端领域已有qiankun.js、MicroApp等成熟方案,但也又一定的局限性,本次实践旨在探索更轻量化的浏览器原生方案——Web Component。作为W3C制定的浏览器原生组件化标准,Web Component具备跨框架UI复用与封装能力,无需依赖第三方框架,可天然实现不同技术栈的融合。 核心目标是将Vue项目打包为可被React调用的Web Component自定义元素,需新增专属入口文件并配置打包规则。 创建 在 注:为简化测试,当前配置未分离Vue运行时依赖,导致最终UMD文件体积偏大。若需优化体积,可通过 React端需通过布局组件控制系统切换逻辑,同时引入Vue打包后的资源文件。 在 在React项目的 至此,基础嵌入功能实现完成,可通过切换菜单验证两侧系统的渲染效果。 Web Component天然支持Shadow DOM,可构建独立DOM树实现样式隔离,避免与React主系统样式冲突;Vue端也可通过Scoped CSS限定样式作用域。但实际业务中常需覆盖子系统样式,结合本次Vue项目使用UnoCSS及CSS变量的特性,采用变量覆盖方案实现样式定制: 样式覆盖需结合项目实际场景调整:若无法通过CSS变量或选择器覆盖,需修改Vue项目源码;若涉及主题切换等动态需求,可通过自定义元素属性传递状态,在Vue端监听属性变化同步更新样式。 UI层嵌入仅完成视觉整合,跨框架逻辑协同的核心在于消息通讯。常用方案包括全局状态共享(挂载至window)、属性传递、事件驱动等,本次实践采用浏览器原生 前文实现了React向Vue发送事件传递Token,但通过 基于Web Component可实现React与Vue跨栈系统的基础融合,通过自定义元素封装、原生事件通讯、CSS变量覆盖等手段,满足核心交互与样式适配需求。但本次实践仍存在诸多待优化点: 相较于qiankun等成熟微前端框架,Web Component也是一种更轻量化的选择方案, 具体实践依然要根据具体的项目情况来选择和评估。当然,后续抽空还会分享一种基于类似门户系统的iframe融合方案,但不会在浏览器打开新页签,大家还有哪些方案可以分享呢,欢迎留言讨论!一、背景与需求

二、技术环境
三、方案选型
四、工程改造实现
4.1 Vue工程改造(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 VueWebComponentElement4.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]'
}
}
} : {})
}
}
})external配置排除Vue核心依赖,但需在React项目中同步引入对应依赖,确保Vue应用运行环境完整。4.2 React工程改造(集成Web Component)
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 AppLayout4.2.2 引入Vue资源
index.html中引入Vue打包后的CSS与JS文件,确保自定义元素可正常渲染:
<!-- 引入Vue Web Component样式 -->
<link rel="stylesheet" href="vue/pvue.css" /<!-- 引入Vue Web Component脚本 -->
五、关键技术点突破
5.1 样式隔离与覆盖
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;
}
}5.2 跨框架消息通讯
CustomEvent实现解耦式通讯。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)
}, [])六、实践总结与待解决问题


























































