前端圈这两年有个话题一直很热:在线表格。从最初能用 JS 读一个 Excel 文件,到在浏览器里做出一个像模像样的工作簿,再到多人在线协作,需求的复杂度一路飙升。如果你也在帮公司做私有化文档、在线填报、数据看板,或者想把表格能力塞进自己的产品里,八成逃不过一个名字:Univer。我最初看到它的时候,正好在找一个既能部署到内网、又能放开手改源码的表格引擎。对比了很多方案之后,Univer 进入视线,最近“univer在线”相关的讨论也越来越多。这篇文章不打算做官方文档翻译,而是结合我自己实际操作下来的理解,聊聊这个开源办公套件引擎到底能干什么、适合什么场景、怎么让它真正在你的应用里跑起来,以及过程中会遇到哪些坑。
Univer 不是另一个“网页版 Excel”,而是一套以 Canvas 渲染为核心的开源办公套件 SDK。和传统 DOM 画表格的方案相比,它天生就更适合大数据量渲染,也因为开放了数据模型、命令系统、插件体系,让二次开发变得更有章法。对于要自建在线办公能力的技术团队,它有很强的吸引力。
1. 认识 Univer:开源办公套件引擎的底层逻辑
1.1 不是又一个在线表格,而是一套可重建的应用框架
第一次打开 Univer 的仓库时,我最直观的感受是:它不是一个独立的成品软件,而是一组可编程的积木。
Univer 用 TypeScript 写了三个核心文档类型:电子表格 Sheet、富文本文档 Doc、幻灯片 Slide,底层统一走一套渲染引擎和数据模型。你可以把它单独当表格用,也可以按需引入文档或幻灯片能力,在同一个 Web 应用中组合出类似 Office 的操作体验。
从技术架构上看,Univer 的分层思路很清晰:
- Domain 层:负责业务概念,比如 Workbook、Worksheet、CellRange、DocContent 等,相当于内存中的业务模型;
- Render 层:基于 Canvas 做绘制,只渲染当前视口内可见的单元格,这也是它能扛大数据量的原因;
- Controller 层:把鼠标、键盘、命令变成业务操作;
- Command 层:统一管理每一个可记录、可撤销、可重放的动作,这个设计对协同编辑尤其重要。
这种分层带来的直接好处是:你不必把所有逻辑都塞进 UI 状态里。Univer 内部把“用户做了什么”抽象成命令,命令触发后再去更新数据模型,渲染层监听数据变化自动重绘。也就是说,你手里有一个非常接近桌面软件架构的 Web 办公引擎,而不是一坨绑定了 DOM 的控件。
1.2 为什么“univer在线”的讨论热度越来越高
“univer在线”这个关键词之所以火,我个人觉得核心原因是三个字:私有化。
很多企业早就不再满足于“把文件存到云盘里”,而是希望把表格能力直接嵌入自己的业务系统,比如财务填报、库存盘点、订单编辑,甚至是从数据库拉一批数据出来,放到表格里让人继续改。这时候,选一个纯前端组件而不是整套 SaaS 产品,往往更现实。
与此同时,主流表格产品要么是闭源商业授权,要么只有基础展示能力。真正开源的优秀方案不多,Univer 是少数把“编辑器体验”和“二次开发能力”同时做出来的项目。它的代码完全开放,社区里也已经有了相当多的实践案例,所以很多人在搜索如何把 Univer 集成到内部系统,这正好对应了“univer在线”这个高频检索词。
提示:如果你只是临时看一下 Excel 文件、做个简单导出,Univer 可能偏重;但如果是成套在线编辑能力、私有化部署、二次开发定制,它很值得放进选型表。
2. 由需求出发的定位:Univer在线到底适合哪些场景
2.1 典型场景图谱
我接触到的 Univer 落地场景,大致可以分为下面几类。
第一类是业务系统内嵌表格编辑。比如企业内部 OA 的报销单、工单系统里的工单表格、ERP 里的物料清单,之前要么用 textarea 硬撑,要么跳转到外部软件编辑,体验割裂。接入 Univer 之后,用户可以像操作真正的工作簿一样填写数据,系统后端拿到结构化的单元格数据再做持久化,体验和开发成本都大大改善。
第二类是数据中台和 BI 看板。BI 项目里经常需要“把查询结果给用户,再让用户自己拖一拖、算一算、导出来”。Univer 的公式引擎可以直接在浏览器端计算,单元格渲染又不受 DOM 数量限制,放几千行数据也能滑动浏览,比传统表格控件要顺畅得多。
第三类是教育、培训、协同编辑平台。在线作业、成绩单、多人同时填报的表格场景,Univer 提供命令系统和协同基础,团队在此基础上接入 WebSocket 或 Yjs 做实时同步,就不需要从零设计一套 OT 数据协议。
第四类是低代码平台和组件市场。很多低代码产品会把 Univer 封装成一个“表格组件”,对上层业务用户屏蔽底层复杂度,对顶层开发者暴露简单的配置参数,比如数据源、列定义、公式开关、权限控制等。
2.2 同类技术方案横评与取舍
说完了场景,再来直面一个问题:凭什么用 Univer 而不用别的?我做了一张横向对比表,方便你按项目情况直接判断。
| 方案 | 开源/商用 | 渲染方式 | 协作能力 | 二开难度 | 适用判断 |
|---|---|---|---|---|---|
| Univer | 开源 | Canvas 自绘 | 具备协同基础,需自行接入传输层 | 中等,架构清晰 | 私有化、深度定制、多文档类型 |
| Luckysheet / x-spreadsheet | 开源 | DOM/CSS 渲染 | Lucky 早期较难做实时协作 | 中低 | 简单展示、原型验证、历史项目 |
| SheetJS / ExcelJS | 开源/商业 | 无 UI,纯解析库 | 无 | 低 | 批量解析、导入导出、后端处理 |
| OnlyOffice | 开源/商用 | 客户端渲染 | 自带协同服务 | 高,整体部署偏重 | 需要完整 Office 套件、偏文档协同 |
| SpreadJS | 商业授权 | Canvas/DOM 混合 | 部分版本支持 | 中 | 预算充足、商业技术支持优先 |
从表格能看出来,如果只是“读读 Excel、导出数据”,完全没必要动用 Univer,一个 SheetJS 就够了。但当你需要“像桌面办公软件一样去编辑”,并且要自己掌控部署位置和代码细节时,Univer 的开放程度和模块化设计就变成了很关键的优势。OnlyOffice 虽然功能完整,但它是一套相对完整的服务栈,嵌进现有前端应用往往要处理更多容器和服务层面的问题,Univer 则更像是可以放进任何前端工程里的库。
2.3 一套快速判断是否该用 Univer 的决策清单
我在选型时总结了一套简单判断方法,你可以按顺序问自己四个问题:
- 你需要的只是“一次导出 Excel”,还是“用户在网页里持续编辑”?前者优先选纯解析库;
- 是否要求私有化部署、代码可控、后续深度改写?如果只是短期展示,商业化控件反而省心;
- 你的实际数据量会不会超过几千行、甚至上万行?会的话,Canvas 渲染比 DOM 渲染更有优势;
- 是否需要多人在线编辑、操作撤销重做、业务命令审计?如果答案是“需要”,那一定要选择有完整命令和数据模型的方案。
这四个问题问完,Univer 适合不适合,心里基本就有数了。
3. 让 Univer 真正“在线”起来:最小落地步骤
3.1 初始化工程与基础依赖
Univer 支持 React、Vue 以及原生 JavaScript 工程,它本身不绑定 UI 框架。下面我以 Vite + React + TypeScript 为例,搭建一个最小在线表格应用。
先用 Vite 创建一个工程:
pnpm create vite univer-demo --template react-ts cd univer-demo pnpm install接着安装 Univer 相关依赖。这里强调一下,Univer 的包名在 0.x 阶段演进比较快,不同版本拆包方式可能有差异,所以你在安装前最好先去 npm 页面确认一下最新版本对应的插件结构。我的经验是以“core + sheets + ui + 对应插件”为最基本的组合:
pnpm add @univerjs/core @univerjs/sheets @univerjs/ui @univerjs/sheets-ui如果后续需要公式、导入导出,再按需追加:
pnpm add @univerjs/sheets-formula @univerjs/sheets-formula-ui @univerjs/sheets-import-xlsx @univerjs/sheets-export-xlsx3.2 用几行代码渲染一个可编辑 Sheet
依赖装好后,核心启动代码要比我预想的简单得多。在App.tsx里写这样一个初始化方法:
import { Univer, LocaleType, UniverInstanceType } from '@univerjs/core'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverUIPlugin } from '@univerjs/ui'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; const univerDemo = new Univer({ locale: LocaleType.ZH_CN, theme: 'default', }); univerDemo.registerPlugin(UniverSheetsPlugin); univerDemo.registerPlugin(UniverUIPlugin); univerDemo.registerPlugin(UniverSheetsUIPlugin); univerDemo.createUnit(UniverInstanceType.SHEET);这段代码做了这么几件事:创建了一个独立的 Univer 实例,注册了表格核心能力、界面框架、表格 UI 交互插件,然后创建了一个工作簿单元。执行之后,页面上就会出现一个可编辑的在线表格,支持选区、输入、撤销、重做,以及基础的格式操作。
如果你希望把表格挂载到指定 DOM 容器,而不要让它占满全屏,可以在初始化时传入容器参数,或者在 Univer UI 插件的配置里指定挂载节点。实际开发中,我通常会预留一个div作为容器,避免和业务页面布局产生冲突。
注意:Univer 一个实例可以创建多个文档单元,但如果在同一页面创建多个独立 Univer 实例,内存开销会有明显增加。多数业务场景更适合保持一个实例,用不同 Unit 管理多工作簿。
3.3 在线导入导出、公式与 UI 插件的接入
“在线”不光指页面能编辑,还包含常见的文件流转:上传一个 XLSX 文件进来,改完再导出去。
导入导出的做法主要是注册对应插件:
import { UniverSheetsImportXlsxPlugin } from '@univerjs/sheets-import-xlsx'; import { UniverSheetsExportXlsxPlugin } from '@univerjs/sheets-export-xlsx'; univerDemo.registerPlugin(UniverSheetsImportXlsxPlugin); univerDemo.registerPlugin(UniverSheetsExportXlsxPlugin);注册之后,Univer 的工具栏会自动帮你把导入、导出入口挂好,不需要自己写文件解析。
公式能力同样通过插件启用:
import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import { UniverSheetsFormulaUIPlugin } from '@univerjs/sheets-formula-ui'; univerDemo.registerPlugin(UniverSheetsFormulaPlugin); univerDemo.registerPlugin(UniverSheetsFormulaUIPlugin);这些都做完之后,输入=SUM(A1:A10)就是可用状态,公式计算在本地引擎完成,无需依赖后端。
我在实际项目里比较推荐“先按最小集跑通,再按需加插件”。Univer 的官方 example 仓库里有大量可运行示例,直接复制能节省很多排查时间,不要一上来就把所有插件全装进去,那样反而不容易定位问题。
3.4 数据持久化与协同的接入方式
Univer 默认更像是“提供了协同能力的地基”,并不是装上包就能多人在线。
它的命令系统会自动监听每一次单元格修改,因此你可以通过监听变更事件,把操作批量提交到服务端。持久化最简单的做法是:当用户停止输入一段时间后,读取当前工作簿的 JSON 快照,发送到后端保存;更精细的做法是,把命令序列或者命令产生的增量数据发给服务端。
协同方向,Univer 社区里常见的是结合 Yjs 这类 CRDT 方案做实时同步。大致链路是:Univer 编辑动作触发本地命令,命令产生数据变更,把变更同步到协作基础设施(如 WebSocket + Yjs Doc),远端客户端收到数据后,再以命令或状态更新的方式应用到各自的 Univer 实例。这样既保留了 OT/CRDT 的一致性能力,又回到了 Univer 的统一命令模型里。
不过我要提醒你一句:**协同的上限取决于你的数据同步层设计,而不是表格组件本身。**权限、冲突策略、离线编辑、服务端存储,这些都需要结合自身业务实现。Univer 能帮你的,是把“格子里的编辑行为”完整暴露出来,至于怎么传输和合并,责任在应用层。
4. 二次开发关键点:命令系统、插件机制与业务封装
4.1 命令系统是把“操作”变成可追溯数据流的关键
如果你只把 Univer 当普通表格控件用,命令系统可能感知不强。可一旦要做业务定制,命令系统会成为你最常接触的部分。
我理解的命令系统可以这样类比:它就是应用里所有操作的“收银台”。用户在界面上点是、输入文字、拖动填充,都会形成一条命令;命令经过执行、校验、分发,最终写入数据模型。这样带来的好处是操作可以被记录、回放和撤销。
在二次开发中,你可以通过监听命令执行结果来感知用户动作:
univerDemo.getCommandService().onCommandExecuted((command) => { if (command.id === 'demo.setCellStyle') { // 业务侧逻辑 } });你甚至可以自定义命令,把业务逻辑和表格操作绑定在同一个流程里。比如点一个“审核通过”按钮,前端先发一个业务命令,命令里同时更新表格单元格的底色和备注,这样整个操作就具备事务性,也能被 Univer 的撤销体系识别。
4.2 插件机制如何优雅扩展能力
Univer 的插件机制很像一个洋葱结构。核心引擎只提供最底层的概念和操作,所有具体功能都以插件方式注入。你可以为团队封装一个“业务插件”,内部同时注册自定义命令、UI 按钮、右键菜单和数据处理逻辑。
我在封装常见表单模板组件时,会把插件拆成两部分:一部分是“能力插件”,负责注册数据校验、单元格权限、外部数据联动;另一部分是“UI 扩展”,负责在工具栏或右键菜单中暴露入口。这样做的好处是,当业务只需要表格能力、不需要那些自定义按钮时,不注册 UI 扩展产物即可,模块之间互不污染。
插件里最常见的扩展点有几个:
- 工具栏按钮:添加一个可点击的入口;
- 右键菜单:针对选区执行自定义操作;
- 命令扩展:注册新的业务命令;
- 渲染层覆盖:对特定单元格做自定义绘制。
实际开发时不需要自己造轮子,官方文档的插件示例已经覆盖了大多数场景,照着写基本都能跑通。
4.3 封装业务组件时需要考虑的边界
当你准备把一个 Univer 功能封装给团队其他人用时,有几个边界问题值得想清楚。
第一个是数据源边界。不要让业务组件直接读取或者修改 Univer 内部数据模型,而应该封装一层方法,比如setCellValue(row, col, value, meta)、readSelectionRange(),这样上层永远只面向业务 API。
第二个是生命周期边界。Univer 实例的创建和销毁要跟随宿主页面路由。单页应用里如果路由切换不销毁实例,多个表格实例会影响性能。建议在组件unmount时调用实例的销毁方法,并且把全局事件监听一并解绑。
第三个是权限控制边界。表格编辑器的能力越强,业务上的安全隐患越多。比如某些用户只能看,某些用户只能填指定列,这些权限通常不建议只在 UI 上隐藏入口,还要在命令层做拦截。Univer 的命令监听机制允许你自定义校验,遇到非法操作直接拦截命令执行,这样比单纯隐藏按钮安全得多。
5. 上线前必须避开的坑:性能与兼容性实录
5.1 大数据量表格渲染会卡?先检查视口
Univer 用 Canvas 虚拟化渲染,并不意味着它天生不会卡。实际使用中,真正的瓶颈往往不是渲染,而是频繁触发全量刷新。
我第一次加载一个几千行的业务数据表时,发现滚动偶尔掉帧,排查后发现问题出在我每次修改单元格后都主动读取了整张表的数据,再调用刷新接口,迫使渲染层重建了整个工作区。后来改成每次只更新目标单元格的值,以及尽量使用数据模型层的批量更新方法,流畅度立刻好转。
如果你的数据量特别大,建议做两件事:一是关闭不需要的单元格事件监听,二是避免在渲染层之外频繁操作大型选区。如果只是展示一个只读大数据表,你也可以考虑先不加载公式插件和 UI 工具栏相关扩展,轻装上阵。
5.2 XLSX 样式差异与导入导出失真
在线表格工具都躲不开一个灵魂拷问:从 Excel 导出的文件,再导入进来会不会乱? Univer 的导入导出做得已经不错,但 Excel 自身的样式规格极其复杂,有条件格式、数据验证、图表、图片、打印设置等,不同实现之间很难做到 100% 一致。
我踩过的坑主要有两个:
第一个是单元格格式偏入差异,尤其自定义数字格式,比如#,##0.00;[红色]-#,##0.00,在部分版本里解析不完整。我现在的做法是在导入之后跑一个巡检逻辑,对关键列做格式归一化,确保业务数据本身的准确性优先于样式还原。
第二个是合并单元格和边框样式。某些边界场景下,导入后边框线宽、颜色会和源文件有细微差别。如果你的产品是给非技术用户做文件交换,建议多做几轮真实文件回归测试,至少保证“数据没错、公式能算、筛选能开”。
5.3 多文档状态管理的几个典型问题
用 Univer 做过多个工作簿的人,大概率遇到过这几个问题。
问题一:看不到某个工作簿的变更。这通常不是表格没改,而是你在多个 Univer 实例之间操纵了错误实例。一个页面一个实例、一个实例多个 Unit 是更推荐的状态。
问题二:撤销操作没有生效。Univer 的撤销栈针对命令,如果你直接通过数据模型 API 修改单元格值,而不是通过命令发生修改,那不会进入撤销栈。业务开发时,能用命令解决的修改就不要绕路。
问题三:自定义事件重复执行。因为前端热更新或监听模块被重复注册,命令监听可能叠加。建议在初始化时统一注册,或者在销毁实例时调用对应移除监听的方法,避免一防。
我在项目里还会额外做一层“工作簿存档”机制:每编辑一条记录,业务侧就收集变更快照,定时上报到后端。这样就算前端出现意外刷新,用户刚改的数据也能从最近一次快照恢复,对在线填报类产品来说非常实用。
实际上手之后,Univer 给我的最大感受是“心智负担比较低”。它的分层设计让我能快速定位一个问题属于数据层、渲染层还是命令层,而不是在一大堆 DOM 事件里挣扎。如果你正准备在内部系统里落地在线表格,或者被老板要求“做一个网页版 Excel 模块”,我建议你先把官方 demo 跑通,然后拿一份自己业务里最真实、最丑、格式最乱的 Excel 文件去导入试一遍,那个结果才最能说明问题。格式、性能、协同这些方向,都可以在验证之后再做取舍。Univer 的门槛不算低,可一旦你适应了它那种模块化、命令化的思考方式,后面扩展起来,会越写越顺。