自定义组件

Web SDK 提供了多种方式来集成自定义组件,使你能够扩展和定制 Web SDK 的功能。本指南将介绍几种常用的方法。

Univer 并不会直接将组件作为参数直接传递给任何渲染函数,你需要通过 Facade API 将组件注册到 Univer 中之后,才能在各个挂载点使用它们。

如果你还不了解如何获取 Facade API 的实例,可以参考 Facade API

注册自定义组件

TypeScript
univerAPI.registerComponent(componentKey, CustomComponent, options)

使用 univerAPI.registerComponent 方法来注册自定义组件。这个方法接受三个参数:

  • componentKey: 组件的唯一标识符,用于在 Univer 中引用该组件。
  • CustomComponent: 组件的实现,可以是 React、Vue 或 Web Components。
  • options: 可选的配置选项,可用于指定组件所依赖的框架或其他相关设置。

React 组件

注册 React 组件无需额外的配置,只需确保组件是一个有效的 React 组件即可。以下是一个简单的示例:

TSX
function ReactComponent(props: Record<string, any>) {  return <div>Hello Univer!</div>}univerAPI.registerComponent('MyReactComponent', ReactComponent)

Vue 组件

Vue 3.x

注册 Vue 组件时,需要确保安装并注册 @univerjs/ui-adapter-vue3UniverVue3AdapterPlugin 插件:

Shell
pnpm add @univerjs/ui-adapter-vue3
TypeScript
import { UniverVue3AdapterPlugin } from '@univerjs/ui-adapter-vue3'univer.registerPlugin(UniverVue3AdapterPlugin)

注册 Vue 组件时,需要指定 framework 选项为 'vue3'

TSX
const Vue3Component = defineComponent({  setup(props) {    return () => <div>Hello Univer!</div>  },})univerAPI.registerComponent('MyVue3Component', Vue3Component, {  framework: 'vue3',})

Vue 2.x

由于 Vue 2.x 已经不再维护,Univer 暂时没有提供 Vue 2.x 的 UI 适配器插件的计划,你可以参考如下代码通过自定义插件来实现一个 Vue 2.x 的适配器:

TypeScript
import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'import { ComponentManager, UniverUIPlugin } from '@univerjs/ui'import Vue from 'vue'/** * The plugin that allows Univer to use Vue 2 components as UI components. */@DependentOn(UniverUIPlugin)export class UniverVue2AdapterPlugin extends Plugin {  static override pluginName = 'UNIVER_UI_VUE2_ADAPTER_PLUGIN'  constructor(    private readonly _config = {},    @Inject(Injector) protected readonly _injector: Injector,    @Inject(ComponentManager) protected readonly _componentManager: ComponentManager,  ) {    super()  }  override onStarting(): void {    const { createElement, useEffect, useRef } = this._componentManager.reactUtils    this._componentManager.setHandler('vue2', (component: any) => {      return (props: Record<string, any>) =>        createElement(VueComponentWrapper, {          component,          props: Object.keys(props).reduce<Record<string, any>>((acc, key) => {            if (key !== 'key') {              acc[key] = props[key]            }            return acc          }, {}),          reactUtils: { createElement, useEffect, useRef },        })    })  }}export function VueComponentWrapper(options: {  component: any  props: Record<string, any>  reactUtils: typeof ComponentManager.prototype.reactUtils}) {  const { component, props, reactUtils } = options  const { createElement, useEffect, useRef } = reactUtils  const domRef = useRef<HTMLDivElement>(null)  useEffect(() => {    if (!domRef.current) return    const Constructor = Vue.extend(component)    const instance = new Constructor({      data: props,    })    instance.$mount()    domRef.current.appendChild(instance.$el)    return () => {      instance.$destroy()    }  }, [props])  return createElement('div', { ref: domRef })}

然后将其注册:

TypeScript
import { UniverVue2AdapterPlugin } from './ui-adapter-vue2'univer.registerPlugin(UniverUIPlugin)univer.registerPlugin(UniverVue2AdapterPlugin)

使用时需要指定 framework 选项为 'vue2'

TSX
const Vue2Component = Vue.component('MyVue2Component', {  template: '<div>Hello, Univer!</div>',})univerAPI.registerComponent('MyVue2Component', Vue2Component, {  framework: 'vue2',})

Web Components

注册 Web Components 时,需要确保组件符合 Web Components 标准,并安装和注册 @univerjs/ui-adapter-web-componentUniverWebComponentAdapterPlugin 插件:

Shell
pnpm add @univerjs/ui-adapter-web-component
TypeScript
import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'univer.registerPlugin(UniverWebComponentAdapterPlugin)

注册 Web Components 时,需要指定 framework 选项为 'web-component'

TSX
class WebComponent extends HTMLElement {  constructor() {    super()    const shadow = this.attachShadow({ mode: 'open' })    const div = document.createElement('div')    div.textContent = 'Hello Univer!'    shadow.appendChild(div)  }}univerAPI.registerComponent('my-web-component', WebComponent, {  framework: 'web-component',})

Angular

通过 @angular/elementscreateCustomElement 将 Angular 组件转换为自定义元素,再使用 Univer 的 Web Component 适配器注册,即可将 Angular 组件用于 Univer 的侧边栏或对话框。

添加与 @angular/core 版本一致的 @angular/elements,以及与其他 Univer 包版本一致的 @univerjs/ui-adapter-web-component

在初始化 Univer 后调用以下函数。传入宿主组件的 Angular 注入器:在宿主组件中使用 private readonly injector = inject(Injector) 获取注入器,再调用 registerAngularPanel(univer, univerAPI, this.injector)。宿主组件中的 injectInjector@angular/core 导入。

TypeScript
import type { Injector } from '@angular/core'import type { FUniver, Univer } from '@univerjs/presets'import { Component } from '@angular/core'import { createCustomElement } from '@angular/elements'import { UniverWebComponentAdapterPlugin } from '@univerjs/ui-adapter-web-component'@Component({  selector: 'app-angular-panel',  standalone: true,  template: '<p>Hello from Angular!</p>',})export class AngularPanelComponent {}export function registerAngularPanel(univer: Univer, univerAPI: FUniver, injector: Injector) {  const element = createCustomElement(AngularPanelComponent, { injector })  univer.registerPlugin(UniverWebComponentAdapterPlugin)  return univerAPI.registerComponent('univer-angular-panel', element, {    framework: 'web-component',  })}

编辑器渲染完成后,使用注册的组件标识作为侧边栏内容:

TypeScript
const sidebar = univerAPI.openSidebar({  header: { title: 'Angular' },  children: { label: 'univer-angular-panel' },  width: 360,})

保存返回的注册句柄和侧边栏句柄,不再使用时调用各自的 dispose()。自定义元素在应用中注册一次即可:名称必须包含连字符,并与 Angular 组件的 selector 不同。

Angular Elements 将输入映射为元素属性,将输出转换为 DOM CustomEvent 事件。Univer 适配器会传递属性,但不会自动订阅 Angular 输出,需要交互时应显式连接这些事件。

使用自定义组件

通过以下方法,你可以灵活地在 Univer 中集成各种自定义组件,从而增强和定制 Univer 的功能。

注意事项

  1. 在使用这些方法时,请确保Univer已经完成渲染。 2. 对于需要注册的组件,请确保在使用前已正确注册。 3. 使用 dispose() 方法来清理和移除添加的组件,以避免内存泄漏。

添加自定义菜单项

顶部菜单栏(Ribbon)和右键菜单(Context Menu)都可以添加自定义组件。为菜单项添加自定义组件需要通过创建自定义插件来实现,我们准备了一份新增自定义菜单项的最佳实践来帮助你快速上手。

替换内置组件

警告

替换内置组件可能会导致一些功能无法正常工作,请在充分阅读源码和文档后自行寻找可替换的组件并谨慎操作。

通过 univerAPI.registerComponent 方法注册组件时,如果传入的 componentKey 已经存在,那么 Univer 会将其替换为新的组件。

例如简单地替换内置的 ColorPicker 组件:

TSX
// 此代码仅可在 UI 层面替换内置 ColorPicker 组件,无法替代其功能实现univerAPI.registerComponent('UI_COLOR_PICKER_COMPONENT', () => <input type="color" />)

作为内容组件添加到……

在侧边栏中使用

使用 univerAPI.openSidebar 方法可以在Univer界面中打开一个包含自定义组件的侧边栏。

TSX
// 你应该在合适的时机(比如渲染完成)注册组件univerAPI.registerComponent('MyCustomSidebarComponent', () => <div>Hello Univer!</div>)const sidebar = univerAPI.openSidebar({  header: { title: 'My Sidebar' },  children: { label: 'MyCustomSidebarComponent' },  onClose: () => {    console.log('close')  },  width: 360,})// 稍后关闭侧边栏sidebar.dispose()

参考: univerAPI.openSidebar

在对话框中使用

使用 univerAPI.openDialog 方法可以打开一个包含自定义组件的对话框。

TSX
// 你应该在合适的时机(比如渲染完成)注册组件univerAPI.registerComponent('MyCustomDialogComponent', () => <div>Hello Univer!</div>)const dialog = univerAPI.openDialog({  id: 'unique-dialog-id', // 对话框的唯一标识符  draggable: true,  width: 300,  title: { title: 'My Dialog' },  children: {    label: 'MyCustomDialogComponent',  },  destroyOnClose: true,  preservePositionOnDestroy: true,  onClose: () => {},})// 稍后关闭对话框dialog.dispose()

参考: univerAPI.openDialog

将弹出框附加到单元格

使用 FRange.attachPopup 方法可以将自定义弹出框附加到特定的单元格范围。

  • Popup 是一个单元格上面依附的临时 DOM,一般用于显示一些临时的状态信息,不支持持久化存储。
  • 使用 FRange.attachPopup(popup: IFCanvasPopup) 方法可以将自定义弹出框附加到特定的单元格范围。
  • Popup 会吸附在单元的四边,如果被遮挡,会自动调整位置和方向。
TSX
// 你应该在合适的时机(比如渲染完成)注册组件univerAPI.registerComponent(  'MyCustomPopupComponent',  () => <div>Hello Univer!</div>,)const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()const fRange = fWorksheet.getRange('A1:J10')// 将弹出窗口附加到范围的第一个单元格// 如果 `disposable` 为 null,则表示 popup 添加失败const disposable = fRange.attachPopup({  componentKey: 'MyCustomPopupComponent',})// 移除弹出框disposable.dispose()

附加警告弹出框

使用 FRange.attachAlertPopup 方法可以在指定范围的起始单元格附加一个警告弹出框。

TypeScript
const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()const fRange = fWorksheet.getRange('A1:B2')const disposable = fRange.attachAlertPopup({  key: 'unique-alert-key', // 唯一标识符  title: '这是一个警告!',  message: '这是一个警告!',  width: 300,  height: 200,  // 0: 信息  // 1: 警告  // 2: 错误  type: 0,})// 稍后移除警告disposable.dispose()

添加浮动 DOM 到工作表

通过 FWorksheet.addFloatDomToPosition 方法可以在工作表上添加一个浮动 DOM。

  • 使用该方法前需要安装 @univerjs/sheets-drawing-ui 插件或 @univerjs/preset-sheets-drawing 预设。
  • 浮动 DOM 是悬浮在工作表上的可拖动组件,同时支持持久化存储。
  • 需要在 Univer 渲染完成之后调用。
  • componentKey: 组件的唯一标识符,用于在 Univer 中引用该组件。
  • 完整的参数定义
TSX
// 你应该在合适的时机(比如渲染完成)注册组件univerAPI.registerComponent(  'MyCustomFloatingDOMComponent',  ({ data }) => (    <div>      Hello      {data?.label}      !    </div>  ),)const fWorkbook = univerAPI.getActiveWorkbook()const fWorksheet = fWorkbook.getActiveSheet()// 添加一个浮动 DOM// 如果 `disposable` 为 null,则表示浮动 DOM 添加失败const disposable = fWorksheet.addFloatDomToPosition({  componentKey: 'MyCustomFloatingDOMComponent',  initPosition: {    startX: 100,    endX: 200,    startY: 100,    endY: 200,  },  // 这是组件的数据  data: {    label: 'Univer',  },})// 移除浮动 DOMdisposable.dispose()

你觉得这篇文档如何?

© 2026 DreamNum Co., Ltd.