使用 @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.name与pkg.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 API的univerAPI.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' });这里的要点:
MyPopup是一个标准的自定义元素类(继承HTMLElement),而不是 React/Vue 组件;registerComponent的第一个参数'my-popup'既是组件在ComponentManager中的注册名,也会被用作自定义元素的标签名(custom element name,按规范必须包含连字符);- 运行时 props 会被直接赋值到创建出的 DOM 元素上,作为 DOM 属性(DOM property)传递,因此支持对象类型的值,例如示例中的
data(可以是{ label: string }这类对象)以及extraProps。这正是它与 React props 传递方式的关键区别——你通过自定义元素类上的set data(value)访问器接收数据。
Facade 方法如何落到ComponentManager
univerAPI.registerComponent是FUniver的 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承担具体的桥接工作(同一文件内),其核心逻辑:
- 校验与注册:要求必须提供
name,否则抛出WebComponentComponentWrapper requires a name prop to define the custom element.;若customElements.get(name)不存在,则调用customElements.define(name, component)完成自定义元素注册(懒注册、去重); - 创建并注入 props:在
useEffect中通过document.createElement(name)创建元素,遍历componentProps,把每个键值对直接赋值为元素的 DOM 属性(webComponentWithProps[key] = value),并跳过key字段; - 挂载与清理:把创建出的元素
appendChild到useRef指向的容器div中,并在 effect 清理函数中removeChild,实现组件卸载时的 DOM 回收;effect 依赖componentProps,props 变化会重新创建并挂载元素。
因此整个链路是:React 渲染容器 →WebComponentComponentWrapper→ 原生自定义元素。Univer 的 UI 服务(弹窗、消息、侧边栏等)无需改动,即可渲染任意 Web Components。
reactUtils(createElement/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/ui的UniverUIPlugin的container配置,把 Univer 渲染进 Web Component 的 shadow/render root 内的#containerId容器; - 适配器插件与
UniverVue3AdapterPlugin并列注册,说明 Univer 的组件框架体系是可叠加的:React 为默认、Vue3 与 Web Components 通过各自 adapter 插件扩展,互不冲突; - 示例还演示了
@lit/react的createComponent反向用法——把 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),仅供参考