普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月28日首页

基于Web Component的React与Vue跨栈系统融合实践

作者 FrankD109829
2026年1月28日 15:57

基于Web Component的React与Vue跨栈系统融合实践

一、背景与需求

最近一直会有一些这样的需求, 两套完全独立的前端系统,分别基于React和Vue框架开发,用户体系及鉴权体系独立,本次测试将尝试把Vue系统嵌入React中,实现核心交互逻辑:点击切换至React系统时,侧边栏(Aside)渲染React菜单,内容区(Content)加载React组件;切换至Vue系统时,侧边栏与内容区同步渲染Vue对应的菜单及组件,形成视觉与功能统一的集成体验,基础UI如下图:

ScreenShot_2026-01-28_155041_722.png

二、技术环境

  • 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变量覆盖等手段,满足核心交互与样式适配需求。但本次实践仍存在诸多待优化点:

  1. 路由兼容性:React采用BrowserRouter(HTML5 History模式),Vue采用HashRouter,两者路由规则冲突,且页面切换时HTML标题同步、路由守卫协同等问题未解决。可通过统一路由模式(如均采用History模式)、主应用接管路由分发实现兼容。

  2. 统一认证体系:两套系统原有独立登录权限机制,目前仅实现Token传递,未完成身份态同步、权限统一校验等功能,需设计跨系统认证中心或共享令牌机制。

  3. 第三方系统改造限制:本次实践基于可自由修改的开源Vue项目,若需嵌入第三方不可控Vue系统,无法进行源码改造,需探索无侵入式封装方案。

相较于qiankun等成熟微前端框架,Web Component也是一种更轻量化的选择方案, 具体实践依然要根据具体的项目情况来选择和评估。当然,后续抽空还会分享一种基于类似门户系统的iframe融合方案,但不会在浏览器打开新页签,大家还有哪些方案可以分享呢,欢迎留言讨论!

❌
❌