☰
Univer开源表格引擎:用插件化架构构建高性能在线Excel应用
2026/11/3 6:22:44 网站建设 项目流程

1. Univer是什么:我在真实业务里被逼出来的选择

先说个背景。去年我在做一个面向中小企业内部的在线数据管理平台,需求说简单也简单:用户打开网页,就能像用 Excel 一样编辑、筛选、汇总表格数据,并且支持多人同时看同一张表。最开始我天真地打算直接用原生 HTML 表格加上一堆输入框去实现,结果数据量一到几千行,页面上所有交互都开始卡顿,键盘输入有延迟,公式函数更是无从下手。

这时我才认真去搜索市面上已有的前端表格方案,最后锁定了 Univer 这个开源项目。它的出现可以说非常及时:针对在线 Excel 这类重交互、重数据、重协作的场景,Univer 直接给了一套开箱即用的开源引擎。简单来说,Univer 是一个基于 TypeScript 构建的插件化办公套件 SDK,核心能力是高性能的在线电子表格(Spreadsheet),同时也在向文档和幻灯片方向扩展,你可以把它理解成一个专门面向 Web 应用的、可以深度定制的“Office 底层内核”。

很多人问,市面上不是已经有 SheetJS、Handsontable、x-data-spreadsheet 这些库了吗?Univer 跟它们有什么区别?我的回答是:Univer 不是又一个“表格控件”,而是一个完整的、具备协同基础和插件体系的办公组件平台。它不仅仅解决“把数据渲染出来”的问题,更把公式引擎、协同编辑能力、权限体系、样式系统、富文本、图表等都作为模块纳入其中,你需要的是一套可被业务长期依赖的底层,而不是一个绑手绑脚的 UI 组件。

这篇文章我会结合自己实际接入 Univer 的过程,从一个开发者的视角完整拆解它的核心价值、架构逻辑、集成踩坑和个人经验。如果你正在调研开源表格方案,或者在纠结怎么给自己的产品加入在线编辑能力,这篇文章应该能帮你少走不少弯路。Univer 官网也提供了在线体验环境,也就是大家常说的 univer 在线演示,动手点几下胜过读一堆文档。

2. 核心架构拆解:Univer 为什么敢做成“大而全”

2.1 插件化架构:先别被“大而全”吓到

Univer 的第一个核心设计是插件化。整套体系由多个包组成,最基础的是@univerjs/core(核心数据模型与命令层),往上依次是@univerjs/sheets(电子表格业务层)、@univerjs/ui(UI 组件层)、@univerjs/facade(外观 API,给非深度用户提供简约接口),再往上还有各种可选插件,比如协同(@univerjs/websocket)、图表、条件格式、数据透视等。

这个架构的最大好处是:你可以只按需加载所需的模块,并不要求把整个 Office 全家桶都装进去。比如你只想做一个简单的数据录入页面,那么只需要core + sheets + ui三个核心包就够了;但如果你要做完整的在线报表分析工具,再往上叠加协同、图表、公式扩展也不迟。这就像点外卖,你可以只点一份米线,也可以加卤蛋加肥肠,店家都支持,但你没必要一上来就全加。

从代码组织上看,Univer 把“数据模型”和“渲染”完全解耦。数据层是纯逻辑的,UI 层只是一个展示壳。这意味着你可以非常容易地写一个自定义 UI 组件来替代默认的工具栏,而表格核心的逻辑、公式计算、数据变更事件依然正常工作。

2.2 基于 Canvas 的渲染引擎:放弃 DOM 是正确决定

在 Web 领域,表格渲染的实现方式一般有两种:一是用真实 DOM 渲染单元格,二是用 Canvas 绘制整张画布。DOM 方案的好处是实现简单、天然支持事件冒泡、方便做无障碍和浏览器查找;但缺点也很致命——当单元格数量达到几万甚至几十万时,DOM 节点数量会拖垮浏览器的主线程,滚动、选中、复制粘贴全都变得卡顿。

Univer 采用的是 Canvas 渲染方案,而且不是简单地把表格画出来,它内部做了一层比较成熟的渲染引擎。这个引擎管理了视口裁剪、脏区重绘、图层合成、交互热区计算等逻辑,保证滚动的时候只绘制可见区域内的内容。你实际滚动一张十万行数据的表格时,体感上跟本地 Excel 相差已经不大。

但要注意:Canvas 方案也意味着 DOM 的天然能力(比如 title 提示、富文本编辑、输入法定位)都需要自己实现或适配。Univer 在输入、选区、批注等方面做了很多工程化处理,但如果你是一个完全依赖浏览器默认行为的开发者,第一次使用可能会觉得“有些细节怎么这么难改”。这其实就是 Canvas 渲染方案的通病,为了性能必须牺牲一部分“原生感”。

用生活化类比解释来说:DOM 方案像用一个个乐高积木搭一座大房子,改起来灵活、但房子一大积木就会塌;Canvas 方案像用画笔在一块画布上画一座房子,画得快、刷新快,但你想单独挪走一根柱子是不可能的,得重画。Univer 选择了这个更难的方案,换取的是大数量级下的稳定体验。

2.3 命令系统与协同基础:把每一次操作变成可同步的记录

协同编辑是 Univer 一个重要的设计伏笔。它的核心数据层并不是简单的“改哪个格子就 set 哪个格子的值”,而是采用了一套**命令系统(Command)结合操作记录(Mutation)**的模式。

简单说,用户在界面上做的每一个动作(比如修改单元格格式、插入一行、删除一列、修改公式),都会被封装成一个结构化的操作对象,先提交给核心的 Command Manager,再由它执行并派发 Mutation 更新数据模型。一切变更都以命令的形式记录在案。这套设计的好处非常多:

  • 支持撤销重做(undo/redo)变得异常简单,只需反向执行上一次命令;
  • 支持协同编辑,不同用户的操作可以通过网络互相广播,并在本地按顺序应用,最终达到一致的状态;
  • 支持审计与回放,所有操作都可以追溯,类似 Git 的历史提交记录,这对企业级场景非常有价值。

如果你熟悉 Redux、Flux 这类前端状态管理,就会发现 Univer 的命令系统本质上是一个更场景化、更领域化的状态管理方案。它不是把所有状态放在一个 store 里,而是定义了表格业务中各种操作语义的 Action,再统一由框架分发执行。

我当时第一次看它的文档,第一反应是“这也太绕了,改个数据为什么要经过这么多层?”但当我真的开始做协同功能时,才体会到这套命令模型的精妙之处:如果没有一层统一的操作抽象,多端同步时会出现大量难以排查的竞态问题。Univer 把这一步踩在了架构层面,而不是事后打补丁,这是它与大多数开源表格库拉开差距的关键点。

3. 从 0 到 1 把 Univer 集成到你的项目里

3.1 最小集成步骤:10 分钟跑起来

Univer 的官方文档写得已经算比较清晰,但如果你从零开始依然会遇到一些包版本、样式引入、容器初始化的小坑。这里记录一套我实测下来的最小可用接入流程。

第一步,初始化一个前端项目。我习惯用 Vite 作为脚手架,具体命令这里就不展开了,流程比较常规。

第二步,安装依赖。以当前稳定版为例,核心依赖如下:

npm install @univerjs/core @univerjs/sheets @univerjs/sheets-ui @univerjs/sheets-formula @univerjs/sheets-numfmt @univerjs/ui @univerjs/facade @univerjs/design

注意几个要点:@univerjs/design是样式与设计 token 包,必须装,否则基础 UI 会错乱;@univerjs/facade建议装,因为它提供的univerapi全局接口能让后续操作方便很多。如果你暂时不需要公式和数字格式,可以省略这两个包,但我建议直接装上,因为后面十有八九会用到。

第三步,初始化一个容器。在 HTML 中放置一个具有明确宽高的根元素,注意这里的容器不是整页,而是一个div,Univer 会在这个容器内挂载画布和 UI。

<div id="univer-container" style="width: 100%; height: 600px;"></div>

第四步,在你的代码中创建 Univer 实例。这里我提供一个非常精简的初始化入口:

import { Univer } from '@univerjs/core'; import { defaultTheme } from '@univerjs/design'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import { UniverUIPlugin } from '@univerjs/ui'; const univer = new Univer({ theme: defaultTheme, locale: 'zhCN', }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverUIPlugin, { container: 'univer-container', }); univer.registerPlugin(UniverSheetsUIPlugin);

上面这段代码创建了一个带有默认 UI 的电子表格实例。这里的核心逻辑是:Univer作为“宿主”,通过注册插件逐步扩展能力。UniverSheetsPlugin提供了表格数据模型能力,UniverUIPlugin负责渲染 UI 壳,UniverSheetsUIPlugin把表格数据和 UI 层接起来。

第五步,创建一个空工作簿并填充一点数据。使用 Facade API 会非常简洁:

const univerAPI = univer.createAPI(); univerAPI.createWorkbook(); const sheet = univerAPI.getActiveSheet(); sheet.getRange(0, 0, 5, 3).setValues([ ['姓名', '部门', '薪资'], ['张三', '市场部', 12000], ['李四', '技术部', 15000], ['王五', '产品部', 13000], ]);

到这里,一个能在页面上渲染、编辑、支持公式输入和基本快捷键的在线表格就已经跑起来了。整个流程从装依赖到页面出现内容,熟练的话十分钟以内可以完成。

3.2 配置项解析:你最应该关心的几个参数

上面只是最小示例,实际项目中往往需要更精细的配置。有几个参数我建议你花时间研究一下,它们的配置直接影响后续的业务扩展。

locale(语言环境):Univer 内置了多语言支持,除了zhCN,还有enUS等。这里有个细节:修改语言时不仅需要传locale参数,还需要确保对应的语言包已经被引入或注册,否则界面可能出现部分英文、部分中文的情况。我在初次接入时就漏掉了语言包的全量引入,导致按钮文案混杂。

container(挂载容器):指定的容器最好有一个明确的宽高。如果你用百分比或 vh 单位,初始化时容器如果还没完成布局,渲染引擎计算视口就会出错。建议在onMounted之后或setTimeout中再初始化 Univer。

theme(主题):Univer 的 UI 支持主题定制,默认提供了defaultTheme,你可以在之上覆盖颜色变量。如果你要做企业级品牌相关的定制,建议提前把主题梳理好,而不是在页面上疯狂写 CSS 覆盖,因为 Canvas 内部渲染的内容是样式隔离的,外部 CSS 根本覆盖不到。

allowSelectingLockedCells / 权限相关配置:如果你要做只能看不能编辑的报表,需要在 WorkSheet 的配置里设置保护区域。这个功能不是一句readonly就能搞定的,它涉及单元格锁定、工作表保护等一套机制,建议在动手前仔细阅读权限相关文档。

3.3 自定义一个简单插件:真正理解 Univer 的扩展方式

光知道读配置还不够,要想让 Univer 真正融入你的业务,必须理解它怎么扩展。Univer 的思想是“一切皆插件”,插件可以注册自己的 Command、MenuItem、UI 组件和数据模型。

举个例子:我们希望给工具栏加一个按钮,点击后把当前选中区域的数据导出为 JSON。常规思路是直接操作 DOM,拿到表格数据然后做处理,但 Univer 里的正规写法是注册一个自定义命令并绑定到 UI 上。

先定义一个命令:

import { CommandType, ICommandService } from '@univerjs/core'; const ExportJsonCommand = { id: 'command.export-json', type: CommandType.COMMAND, handler: async (accessor) => { const commandService = accessor.get(ICommandService); // 在这里读取当前选中区域、获取数据、导出 JSON return true; }, };

然后注册到命令服务里,并在菜单配置中追加按钮入口。这套机制的清爽之处在于:业务逻辑只需要关心命令本身,UI 层通过声明式配置与命令绑定,数据、命令、UI 三者彻底解耦。

自定义插件是 Univer 的进阶必修课。我建议你从这类小插件开始练手,逐步理解它的事件、服务和数据模型。你在插件里可以通过getInjector()获取到各种服务,比如ICommandService、IConfigService、IEditorService等,这种依赖注入的方式一开始可能有点不习惯,但它能让代码的可测试性变得非常好。

4. 我在实际接入中踩过的大坑和小坑

4.1 性能瓶颈:不是 Univer 慢,是我的写法太粗暴

刚开始我把 Univer 接入到一个需要频繁更新表格数据的页面:后端每 5 秒推送一批数据,我用 API 循环去setRangeValues更新单元格。结果页面肉眼可见地掉帧,CPU 占用率直线上升。

排查后发现问题不在渲染引擎,而在数据更新方式。Univer 每次更新单元格都会触发一整套命令分发、Mutation 派发、渲染引擎重绘的流程。如果你用几十次循环去更一个 20 行 5 列的区域,相当于连续触发几十次完整链路。改用一次批量setRangeValues把整个区域的数据一次性传入,性能立竿见影,CPU 占用几乎可以忽略不计。

这个经验对任何表格级操作都适用:尽量批量,避免频繁的小粒度更新。这也符合 Canvas 渲染引擎的重绘机制,一次大范围脏区重绘的开销远小于多次小范围重绘的开销总和。

4.2 公式与数据格式:有两套独立的“数据宇宙”

另一个容易踩的坑是公式和数值格式。Univer 的公式引擎是独立的包,它维护一套“计算链”,与单元格的文本值是两个维度的数据。当你通过 API 修改一个单元格的值为普通字符串时,公式引擎不会参与计算;但当你设置一个公式字符串时,公式引擎会接管计算,并把计算结果填充到单元格的展示层。

我在做数据导入功能时,就用普通字符串的方式写入了一列=SUM(A1:A5),结果它被当成纯文本显示在界面上,完全没有参与计算。后来才发现,需要用公式 API 或特殊的数据结构来告诉 Univer“这是一个公式”。这个细节文档里写得很含蓄,但实际开发中一定是必踩坑。

数值格式(@univerjs/sheets-numfmt)也类似:它是独立于显示值和底层数值的存在。你在界面上看到“31.2%”这种格式,底层存的值其实是 0.312;如果要通过 API 读取真实数值做计算,需要注意读的是 format 前的原始值还是显示值。

4.3 协同与数据同步:别想着自己裸写 WebSocket

Univer 有协同编辑能力,官方提供的实现里有底层的 OT(Operational Transformation)变换基础,但如果你要自己接入一套协同后端,那绝对是深水区。官方文档坦白地说,协同涉及的内容远不止前端代码,服务端需要处理操作合并、冲突解决、版本管理和心跳保活等一堆事情。

我的建议是:如果业务场景只是“多人查看同一份数据”,用 Univer 默认的展示能力加上你后端的数据订阅推送就够了;如果要做真正的多人同时编辑,请优先考虑官方提供的协同示例和配套方案,不要尝试用 WebSocket 裸写一套 OT 出来。这个坑我进去过,花了两周时间才确认它值得交给一个更成熟的方案,而不是自己造轮子。

4.4 样式覆盖:别试图用 CSS 去改 Canvas 画出来的东西

前面提到了,Univer 默认 UI 是 DOM,但表格画布、选区高亮、网格线这些都是 Canvas 渲染出来的。CSS 只能触达 DOM 部分,无法改动画布内的样式。想要自定义网格线颜色、选区颜色、单元格样式,应该通过 Univer 的主题变量或 API 配置去修改,而不是在全局样式里写.univer-grid-line { color: red }这种无效代码。

我一开始想改选中区域的蓝色高亮,往全局样式表里加了一堆规则,完全没效果,后来查文档才发现它是通过theme配置里的--primary-color系列变量控制的。Univer 的主题 Token 覆盖能力很强,但必须在初始化时注入,不能事后动态修改全局 CSS 变量。

4.5 一些小的体验细节

再说几个容易被忽略的细节。第一,默认情况下 Univer 的右键菜单是英文的,需要额外配置locale和语言包。第二,代码块、批注、富文本在 Canvas 模式下与小部分输入法存在兼容性问题,中文输入法偶尔会出现候选框定位偏移,可以考虑在业务层引导用户使用内置的全屏编辑框。第三,Univer 对移动端触屏支持不算特别好,如果你要做移动端 H5 表格,建议先用体验 demo 在真机上测试一下手势、缩放、滚动是否满足需求。

这些都是实际使用中才会暴露的问题,但整体来说可控。任何复杂前端库都会有类似的计算,重要的是明白哪些事情能自己做,哪些事情应该顺着框架走。

5. 我为什么建议你把 Univer 作为在线表格的长期底座

5.1 与其他开源方案的横向对比

网上经常有人问 Univer 和 luckysheet、x-data-spreadsheet 这些库怎么选。我个人的评估维度很简单:看你要做的东西未来会不会长大。

如果只是做一个一次性的小工具,或者只需要简单的数据展示和少量编辑,用 x-data-spreadsheet 或 Handsontable 这类轻量库完全够用,上手成本更低。但如果你要做的是在线 Office 级别产品,要支持多人协同、复杂公式、数据透视、权限控制、历史版本,那 Univer 比大多数开源方案走得更远。

我在调研阶段做了个简单对比,这里分享给大家参考:

维度UniverLuckysheetx-data-spreadsheetHandsontable
渲染方式Canvas 自研引擎CanvasCanvasDOM + Canvas(混合)
协同能力内置 OT 概念与协作架构社区协议不统一几乎无商业版支持
插件体系强,依赖注入规范弱,修改源码居多弱中
社区活跃度高,迭代快维护趋缓很低商业为主
学习成本偏高低低中

但这里要声明一下,这个对比仅代表我个人的实际体验,不代表任何劣优判断。技术选型没有绝对正确,只有适合与否。如果你团队只是应急要做个内网小工具,我不建议你为此引入一套完整框架,那是用牛刀杀鸡。

5.2 Univer 在企业级场景中的落地想象

我个人的判断是,Univer 最适合的场景是那种你需要“长期演进”的产品。举个例子:一个企业内部的数据分析平台,最开始只需要几张报表表格,但后面一定会加数据权限、导出、透视、图表、多人协同、审计追溯。如果从一开始就建立在 Univer 之上,这些能力都会以“插件包”的方式逐渐叠加,而不是推翻重来。

Univer 的优秀实践还包括它的 npm 包划分。它没把所有代码打包成一个笨重的全量库,而是拆成了几十个小包。这对构建体积和按需加载相当友好,在业务部署时可以用 tree-shaking 把无用的模块剔除掉。一个只用了基础表格能力的项目,最终 gzip 后的体积是可控的,不会像某些办公套件一样动辄几兆。

我现在负责的项目已经稳定运行了半年多,Univer 作为核心表格引擎没有出现一次致命性故障。期间一次大版本升级,也走得比较体面:类型定义相对完善,升级主要注意了包名和插件注册方式的变化。相比一些个人开源项目“修个 bug 引发三个新 bug”的情况,Univer 整体工程成熟度算是很不错的。

5.3 未来扩展方向

如果你也打算用 Univer,我建议你优先关注这几个扩展方向:一是自定义公式,Univer 允许注册自己的函数并在公式引擎中使用,这对财务、统计类业务非常有用;二是自定义单元格渲染器,你可以在单元格里渲染自定义图形,比如走势图、标签、状态点,这是把表格做成业务数据台的利器;三是权限体系,通过工作表保护和区域权限配置,可以实现“部分人可编辑、部分人只读”的精细化控制;四是离线数据导入导出,官方支持部分 xlsx 能力,但复杂 Excel 文件的完美转换仍依赖社区生态的持续完善。

我个人理解,Univer 本质上是在“把桌面办公软件搬上 Web”这个方向上努力,目前它已经走完了从 0 到 1 的路径,后续的生态会越来越丰富。

6. 一些真正干过活的人才会跟你说的建议

如果看到这里你决定试一下 Univer,我最后再分享三条个人经验。

第一,先跑官方在线 demo,但别只点两下就下结论。把真实业务中的数据复制一份放进去,试着做你业务里最复杂的操作,比如批量导入、跨表引用、复杂公式。只有用真实数据压过,才能感知到这个引擎的边界。

第二,尽早熟悉 Facade API,它真的能救你的命。Univer 底层的服务、命令、依赖注入体系对新手来说有点繁琐,但createAPI提供的那层外观接口非常简洁。大部分常规操作在这里都有对应方法,写起来快得多。遇到 Facade 没覆盖的能力,再往下找文档也来得及。

第三,复杂项目优先关注版本。Univer 目前还处于快速迭代阶段,版本升级时偶尔会有破坏性变更。团队如果决定使用,建议锁定依赖版本,重要升级要评估影响面。同时关注它的官方 release notes,避免在没有任何准备的情况下升级主版本。

聊到最后,回到标题本身。Univer 并不是一个“能跑就行”的小工具库,它代表的是 Web 办公领域新一代基础能力。作为开发者,我认为选择它的价值不在于今天能省多少事,而在于未来不用推翻重来。这一点,等你真正把一个在线表格模块做完、上线、接受真实用户蹂躏之后,自然会有体会。

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

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

立即咨询