使用 @univerjs/ui-adapter-web-component 将 Univer UI 接入 Web Components 生态
2026/9/15 1:07:13 网站建设 项目流程

使用 @univerjs/ui-adapter-web-component 将 Univer UI 接入 Web Components 生态

【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer

导读

@univerjs/ui-adapter-web-component是 Univer 官方提供的 UI 适配器插件,它让 Univer 的 UI 服务可以直接把Web Components(自定义元素)当作 UI 组件来注册与渲染,从而摆脱对特定框架组件的强绑定。本文以该包的 README 为骨架,结合仓库源码,讲解包的安装、插件注册、web-componentframework 的用法、运行时 props 以 DOM 属性传递的机制,以及底层ComponentManager与包装组件的实现原理,并给出一个完整的 Lit 自定义元素集成示例,帮助你掌握在非 React 技术栈中扩展 Univer UI 的实战方案。

包概览:定位与产物

该包在仓库中以 monorepo workspace 方式维护,对应目录为 packages/ui-adapter-web-component,包的元信息见 package.json:

包名@univerjs/ui-adapter-web-component
UMD 全局变量UniverUiAdapterWebComponent
CSS无(不内置样式)
Locales无(不内置多语言资源)
Facade 入口

从表格可以看出,这个包是纯逻辑适配层:不携带样式、不携带语言包、不扩展 Facade API,职责单一——为ComponentManager提供一个能把自定义元素渲染进 Univer UI 的 framework 处理器。包本身只依赖@univerjs/core@univerjs/ui两个核心包(见 package.json 的 dependencies 字段)。

仓库当前版本为1.0.0-beta.2,与整个@univerjs/*系列保持一致。

安装

使用你习惯的包管理器安装:

pnpm add @univerjs/ui-adapter-web-component # or npm install @univerjs/ui-adapter-web-component

需要注意:Univer 所有@univerjs/*包必须保持在同一版本,否则可能出现依赖不兼容导致的运行时错误。这也是 monorepo 工作区(workspace:*依赖)在发布后的使用约束。

快速开始:注册适配器插件

安装完成后,在初始化Univer实例时注册插件即可:

import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'; univer.registerPlugin(UniverWebComponentAdapterPlugin);

插件类UniverWebComponentAdapterPlugin定义于 plugin.ts:

  • 插件名称为'UNIVER_UI_ADAPTER_WEB_COMPONENT_PLUGIN',包名与版本号分别取自pkg.namepkg.version
  • 构造时通过merge({}, defaultPluginConfig, this._config)合并用户配置,并以UI_ADAPTER_WEB_COMPONENT_PLUGIN_CONFIG_KEY(值为'ui-adapter-web-component.config',见 config.ts)写入IConfigService。当前版本的IUniverWebComponentAdapterConfig是空接口、defaultPluginConfig为空对象,意味着该插件目前无需任何必填配置,后续版本可平滑扩展。

核心用法:以web-componentframework 注册自定义元素

注册插件之后,就可以通过Facade APIuniverAPI.registerComponent注册自定义元素,并通过framework: 'web-component'选项声明该组件是一个 Web Component:

class MyPopup extends HTMLElement { set data(value) { this.textContent = value?.label ?? ''; } } univerAPI.registerComponent('my-popup', MyPopup, { framework: 'web-component' });

这里的要点:

  1. MyPopup是一个标准的自定义元素类(继承HTMLElement),而不是 React/Vue 组件;
  2. registerComponent的第一个参数'my-popup'既是组件在ComponentManager中的注册名,也会被用作自定义元素的标签名(custom element name,按规范必须包含连字符);
  3. 运行时 props 会被直接赋值到创建出的 DOM 元素上,作为 DOM 属性(DOM property)传递,因此支持对象类型的值,例如示例中的data(可以是{ label: string }这类对象)以及extraProps。这正是它与 React props 传递方式的关键区别——你通过自定义元素类上的set data(value)访问器接收数据。

Facade 方法如何落到ComponentManager

univerAPI.registerComponentFUniver的 UI Mixin 方法(见 f-univer.ts):

override registerComponent(name: string, component: any, options?: IComponentOptions): IDisposable { const componentManager = this._injector.get(ComponentManager); return this.disposeWithMe(componentManager.register(name, component, options)); }

它最终调用ComponentManager.register。在 component-manager.ts 中,register的默认 framework 是'react'

register(name: string, component: ComponentType, options?: IComponentOptions): IDisposable { const { framework = 'react' } = options || {}; if (framework === 'vue3' && !this._handler.vue3) { throw new Error('[ComponentManager] Vue3 support is no longer built-in since v0.9.0, please install @univerjs/ui-adapter-vue3 plugin.'); } // ... this._components.set(name, { framework, component }); }

注册后,任何使用该组件名的 UI 场景(弹窗、侧边栏、通知等)在渲染时都会调用ComponentManager.get(name),根据framework找到对应的 handler 进行适配。若找不到 handler,会抛出[ComponentManager] No handler found for framework: ...错误——这也是必须注册本适配器插件的原因。

原理剖析:插件如何把自定义元素桥接到 React 渲染

Univer 的 UI 层基于 React 构建,而 Web Components 原生不参与 React 的虚拟 DOM。适配器在onStarting生命周期中为ComponentManager注入'web-component'处理器,代码位于 plugin.ts:

override onStarting(): void { const { createElement, useEffect, useRef } = this._componentManager.reactUtils; this._componentManager.setHandler('web-component', (component, name) => { return (props) => createElement(WebComponentComponentWrapper, { component, props: { name, componentProps: props, }, reactUtils: { createElement, useEffect, useRef }, }); }); }

随后WebComponentComponentWrapper承担具体的桥接工作(同一文件内),其核心逻辑:

  1. 校验与注册:要求必须提供name,否则抛出WebComponentComponentWrapper requires a name prop to define the custom element.;若customElements.get(name)不存在,则调用customElements.define(name, component)完成自定义元素注册(懒注册、去重);
  2. 创建并注入 props:在useEffect中通过document.createElement(name)创建元素,遍历componentProps,把每个键值对直接赋值为元素的 DOM 属性webComponentWithProps[key] = value),并跳过key字段;
  3. 挂载与清理:把创建出的元素appendChilduseRef指向的容器div中,并在 effect 清理函数中removeChild,实现组件卸载时的 DOM 回收;effect 依赖componentProps,props 变化会重新创建并挂载元素。

因此整个链路是:React 渲染容器 →WebComponentComponentWrapper→ 原生自定义元素。Univer 的 UI 服务(弹窗、消息、侧边栏等)无需改动,即可渲染任意 Web Components。

reactUtilscreateElement/useEffect/useRef)来自 component-manager.ts 中ComponentManager.reactUtils字段,它把 React 的最小渲染原语以依赖注入方式提供给适配层,使本包无需直接 import React 也能完成桥接。

实战:用 Lit 自定义元素承载整个 Univer 实例

仓库的 examples/src/sheets-webcomponent/main.tsx 给出了一个完整的端到端示例:把整个 Univer 工作表封装成一个自定义元素<my-univer>,并在其中注册 Web Component 适配器插件。

核心步骤:

@customElement('my-univer') class MyWebComponent extends LitElement { override firstUpdated() { const container = this.renderRoot.querySelector('#containerId') as HTMLDivElement; const univer = new Univer({ locale: LocaleType.ZH_CN, locales: { [LocaleType.ZH_CN]: zhCN }, logLevel: LogLevel.VERBOSE, }); // 基础插件:网络、公式引擎、渲染引擎、UI 插件(容器指向 Web Component 内部 div) univer.registerPlugin(UniverNetworkPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverRenderEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container, ribbonType: 'classic', }); // ... 文档 / 表格 / 各功能插件 ... // 适配器插件:让 Univer UI 服务支持 Web Components 组件 univer.registerPlugin(UniverWebComponentAdapterPlugin); univer.createUnit(UniverInstanceType.UNIVER_SHEET, DEFAULT_WORKBOOK_DATA_DEMO); window.univerAPI = FUniver.newAPI(univer); } override render() { return html` <link rel="stylesheet" href="./main.css"> <div style="height: 100%;" id="containerId" /> `; } }

要点解读:

  • @univerjs/uiUniverUIPlugincontainer配置,把 Univer 渲染进 Web Component 的 shadow/render root 内的#containerId容器;
  • 适配器插件与UniverVue3AdapterPlugin并列注册,说明 Univer 的组件框架体系是可叠加的:React 为默认、Vue3 与 Web Components 通过各自 adapter 插件扩展,互不冲突;
  • 示例还演示了@lit/reactcreateComponent反向用法——把 Lit 自定义元素封装成 React 组件以便在 React 宿主中挂载,适合从 React 项目平滑引入 Web Components。

使用建议与注意事项

  • 组件名即标签名registerComponent的名字必须是合法的自定义元素名(含连字符,如my-popup),并且最终通过customElements.define注册到全局 registry,注意避免与页面中已有元素重名(重名时customElements.define会抛错,适配器已用customElements.get做了去重保护);
  • 数据传递走 DOM 属性:props 以属性而非 HTML attribute 的方式赋值,所以可以传递对象、函数等复杂值;但这也意味着自定义元素需要通过set xxx(value)访问器主动接收,并自行处理渲染(如示例中把data.label写入textContent);
  • 生命周期由 React 驱动:元素创建、挂载、卸载完全由WebComponentComponentWrapper的 effect 管理,自定义元素本身遵循标准 Web Components 生命周期,二者通过 DOM 边界解耦;
  • 包体积友好:本包不携带 CSS 与 locales,不引入额外 UI 资源,适合按需集成。

总结

@univerjs/ui-adapter-web-component通过一个轻量的插件 + 包装组件,把 Univer 的ComponentManager组件体系扩展到 Web Components 生态。无论是为现有 Univer 应用接入自定义弹窗、消息通知,还是把整个 Univer 实例封装成可与任意框架共存的自定义元素,它都提供了标准、可复用的桥接方案。配合 plugin.ts、component-manager.ts 与 main.tsx 三个文件,即可完整掌握从注册、适配到端到端集成的全部链路。

【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询