最近后台一直有人在问 univer 到底是什么,我说这是个前端开源项目,做在线表格的,对方下一句大概率是:那我能直接拿它替换 Excel 吗?问的人多了,我都想把答案统一发在这里。Univer 这个项目,准确说是一套面向 Web 的文档数据引擎,最早被大家关注是因为它在浏览器里把表格做得足够顺滑,尤其是大数据量滚动和公式计算的表现;后来大家发现它不止是表格,还有文档、幻灯片,甚至能往协同编辑方向走,讨论热度就上来了。如果你正在做企业应用、低代码平台、数据分析后台,或者单纯想找个能嵌进自己系统的表格组件,Univer 值得你花一个周末仔细试一遍。这篇文章我会按自己的集成经验来讲,先说它到底是什么,再讲核心设计怎么理解,然后给可复现的接入步骤,最后是实操里踩过的坑和“univer 在线”这类需求的落地思路。
1. Univer 是什么,为什么说它不是普通表格组件
1.1 多数人的入门动机:替换项目里那份“难用”的表格
我在各种项目里见过很多处理表格的方式。早期的系统喜欢用<table>标签硬画,导出的竟然是浏览器打印出来的假 Excel。后来有人用 iframe 嵌 SheetJS 的在线编辑器,或者引一个重量级商业控件,功能是不少,但每次改需求都痛不欲生。Univer 出现在视线里以后,第一反应是“这又是个表格插件”,但真正动手才发现,它跟普通表格库不是一个物种。
普通表格组件解决的是“能展示、能填数、能导出”这三个问题,Univer 解决的是“整个文档生命周期怎么在浏览器里跑起来”的问题。它能做的包括:创建和渲染电子表格、处理公式计算、响应用户编辑、把数据模型暴露给开发者,甚至支持多人在同一份数据上操作。这些能力听起来像 Excel,但它本质上不是成品应用,而是一套你可以按需拼装的积木。
1.2 它的真实身份:一套文档基础设施
Univer 在 GitHub 上的包名基本都带@univerjs前缀,核心、公式引擎、渲染引擎、UI、文档、表格、幻灯片全是独立模块。这意味着你可以只用它的渲染引擎,自己写一套工具栏;也可以只用它的数据模型,把界面完全替换成自己公司的设计规范。我第一次看到这种拆分时,第一反应是“项目重构起来会不会很麻烦”,实际用下来反而觉得清爽。
从项目定位看,Univer 更像是在做一个“浏览器里的 Office 运行时”。它不只是给你一个好看的网格界面,更重要的是给你一套统一的数据结构。这一套数据结构可以描述单元格、区域、样式、合并单元格、行列尺寸,还能反过来承载公式和协同操作。理解这一点,才能明白后面为什么它能做协同,为什么不同模块能共享一套数据源。
1.3 和传统表格组件比,优势体现在哪
拿国内开发者用得比较多的 Luckysheet、Handsontable、商业的 SpreadJS 来对比。Luckysheet 轻量、社区活跃,但公式能力和复杂协同相对弱,更像一个前端的展示型表格。Handsontable 更适合数据录入,操作体验和 Excel 差异比较大。SpreadJS 功能全面、Excel 兼容性强,但是商业化收费,授权谈判周期也不短。Univer 的特点是开源、模块化、跨类型,加上官方在主推协同能力,在这个领域里属于很特殊的位置。
选型没有绝对好坏,要看场景。如果项目只要求一个可编辑的业务表格,Univer 反而是杀鸡用牛刀。但如果你预判未来要在表格里加公式、加跨单元格联动、甚至要多人同时编辑,那从一开始用 Univer 这种可扩展架构,后面会少走很多弯路。
2. 想用好 Univer,得先看懂这四个核心设计
2.1 插件化架构:核心和 UI 完全解耦
Univer 的架构核心是插件机制。你创建一个Univer实例,然后按需注册 UI 插件、渲染插件、表格插件、公式插件。核心引擎不关心界面长什么样,也不关心你用 React、Vue 还是原生 JS。这个设计带来的直接好处就是:公司想改 logo、改交互、改按钮位置,完全不影响数据计算逻辑。
我当时做了一个自定义工具栏的尝试,过程比想象中简单。把UniverUIPlugin的默认按钮隐藏掉,然后在自己的 Vue 组件里调用 commands 操作工作表,Univer 内部会通过命令系统统一处理,所有操作都会进入撤销栈和事件流。这种解耦在商业控件里通常做不到,因为商业控件往往把界面和逻辑焊死在一起,定制能力受限。
插件化架构还有个隐藏优势:测试。你可以只注册核心模块和渲染引擎,在 Node 环境里跑单元测试,不需要启动浏览器看界面。团队里如果对代码质量要求高,这个特性很有价值。
2.2 渲染引擎:Canvas 上的虚拟表格
Univer 的表格渲染主要靠 Canvas,不是传统 DOM。这一点很多人会忽略,但实际影响非常大。一个普通表格如果渲染 5000 行,DOM 节点数量会直接卡爆浏览器;Canvas 只需要一块画布,按可视区域重绘,滚动时只画看到的部分,所以几万行数据也能保持流畅。
用 Canvas 渲染的另一面是“你不能直接操作单元格的 DOM”,自定义单元格内容需要走渲染器机制。举个例子,我想在某个列里画一个进度条,传统做法是放一个元素到单元格里,Univer 的做法是注册一个自定义 renderer,在 Canvas 绘制阶段把进度条画上去。这个学习成本有点高,但换来的是性能提升。
当前端表格进入“十万行级别”的时候,Canvas 虚拟渲染几乎是必然选择。Univer 在这一块做得比较彻底,网格线、选区、编辑器、浮动元素都通过渲染引擎统一绘制,架构上保持一致,避免了不同渲染方式之间的性能损耗。
2.3 公式引擎:异步化和扩展能力
Univer 的公式引擎不是简单把 Excel 公式翻译成 JS,它把公式计算做成了独立模块。这个模块支持异步公式函数,也就是单元格公式可以等待网络请求、等待后端返回结果,然后重新计算。这一点对业务系统有实际意义。比如单元格里填一个“查汇率”的自定义函数,函数内部调接口拿实时汇率,结果可以自动更新,这在传统表格组件里很难做。
公式引擎另一个关键是跨表格引用。Univer 支持在多个工作表之间用公式引用,比如SUM(Sheet2!A1:A10),日常财务场景基本覆盖了。引擎底层还做了依赖树和计算优化,修改一个单元格时,只会重算依赖它的公式链,不是全表重算。
如果有兴趣,可以注册自定义函数。我试过写一个根据工龄计算年假的函数,注册以后和内置函数一样使用,整个过程允许传参、返回数组、参与嵌套计算,体验相当顺滑。这块设计让 Univer 不再只是一个展示工具,而是真正能参与业务逻辑的计算平台。
2.4 协同层:Univer 给的是协议,不是完整产品
很多人以为 Univer 装上就能部署一个“在线协同表格”,这是个误区。Univer 的协同更多是提供了前端侧的协议、操作转换和同步能力,真正的服务端消息广播、房间管理、历史版本保存需要你自己实现或者用官方示例扩展。
协同的核心难点不是 WebSocket 连接,而是操作冲突。两个人同时改同一个单元格,谁覆盖谁?A 在第 3 行插了一行,B 在第 10 行输入了数据,这行数据应该插到哪里?Univer 在数据结构层面提供了 operation 体系,每个编辑动作都是一个可序列化的操作对象,向后端传递这些操作,服务端做合并和广播,前端再做应用。这个思路是对的,但落地时你要自己解决存储、鉴权、断线重连等问题。
我的建议是:第一版先做单机编辑,把编辑事件同步到后端保存;等业务确实需要实时协作时,再引入协同协议层。协同上线前一定要先想清楚“冲突处理策略”,否则上线后会出现用户数据互相覆盖的严重事故。
3. 把 Univer 装进自己项目的完整过程
3.1 开始前的准备:版本选择和依赖安装
Univer 迭代速度很快,不同大版本之间 API 差异不小。我建议在动手前先固定一个版本,并且以后每次升级时单独安排时间。直接用最新代码,大概率会踩到文档跟不上代码的坑。
我用 Vite 搭了一个最小工程,操作比较直接:
npm create vite@latest univer-demo -- --template vanilla-ts cd univer-demo npm install @univerjs/core @univerjs/engine-render @univerjs/ui @univerjs/sheets @univerjs/sheets-ui @univerjs/engine-formula @univerjs/sheets-formula如果你用的版本和我的不同,包名可能略有调整,比如后面官方推出了预设包,安装命令更简洁。但从这组包名也能看出 Univer 的模块化风格:核心、渲染、UI、表格、公式各自独立,你可以按需安装。
3.2 最小可运行示例:五步画出第一个表格
以当前主流版本为例,初始化逻辑大概是这样的。先创建一个 HTML 容器,比如<div id="app" style="width:100%;height:600px;"></div>,然后在src/main.ts里初始化:
import { Univer, UniverInstanceType } from '@univerjs/core'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; const univer = new Univer({ locale: 'zhCN', }); univer.registerPlugin(UniverRenderEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container: 'app', }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverSheetsFormulaPlugin); const workbookData = { sheets: [ { id: 'sheet1', name: 'Sheet1', cellData: { A1: { v: 'Hello' }, B1: { v: 'Univer' }, A2: { v: '在线表格' }, }, }, ], }; univer.createUniver({ id: 'workbook-1', type: UniverInstanceType.UNIVER_SHEET, data: workbookData, });这个示例做了一件事:创建了一个工作表,填了三个单元格。运行起来后,你能看到表格、能编辑、能滚动,这就是最基础的一步。后面所有复杂功能都建立在这个流程上:新 Univer 实例、注册插件、塞入工作簿数据。
这个流程解释清楚了一个关键点:Univer 不是靠外部调用“打开某个文件”来工作的,而是直接把数据对象传给实例。数据模型就在前端,即时渲染,性能自然比加载 iframe 页面快。
3.3 给表格喂数据、读数据,两种和业务对接的方式
开发业务系统最关心的是怎么把后端数据填进去,以及用户编辑完怎么拿回来。Univer 官方有命令体系,我自己的实践是这样做的。
填数据时,不要直接操作渲染层,而是构造一份工作簿数据传给createUniver,或者用命令 API 修改单元格内容。工作簿数据里的cellData就是单元格字典,键是A1这种坐标,值里有v表示内容,还能带s表示样式对象。这么做的好处是数据模型和界面分离,后端给什么,前端就按结构映射,不容易出现界面和数据对不上的问题。
读数据时,关键不是监听界面上的输入事件,而是监听 Univer 的命令流。用户每一次编辑都会产生命令对象,里面记录了范围、值、旧值、新值这些完整信息。你可以把这些命令广播给后端,或者用防抖方式定期整表快照保存。如果只保存最终值,会丢失编辑历史;如果保存命令流,以后能做撤销远端的操作,这才是“在线化”的正确姿势。
我的经验是:第一版先用整表快照保存,结构简单,维护成本低;等操作量大了,再升级为命令流同步。快照方式最容易出错的是 JSON 深层引用,保存前一定要做一次深拷贝,避免把前端运行时对象直接传给后端。
3.4 导入导出和“univer 在线”的最小闭环
Univer 前端适合做编辑和展示,但 XLSX 文件的解析生成,纯前端处理并不划算,特别是遇到大文件、复杂样式时会很吃力。我更推荐服务端异步处理:前端把工作簿数据 JSON 发给后端,后端用转换服务生成 XLSX;反过来,后端把 XLSX 解析成 JSON 再返回前端渲染。
“univer 在线”这个热词其实描述的是一个完整场景:用户在浏览器里打开表格,实时编辑,数据落库,多人可见。最小闭环可以这样做:
- 前端初始化 Univer,加载业务数据。
- 用户编辑,前端监听命令流。
- 命令通过 WebSocket 发送到服务端。
- 服务端校验并应用操作,广播给同房间其他人。
- 前端收到远端操作后通过命令 API 应用到本地。
我把这个链路拆成两步走:第一步先只做第 2、第 3、第 5 步,也就是保存和回放;第二步再加服务端广播和冲突处理。这样即使协同暂时没做完,单机的“保存-恢复”功能已经能交付使用。
4. 实操里最容易翻车的 5 个问题(排查记录)
4.1 一上来就配协同,结果把自己卡死
我见过不少人第一次上手 Univer 就直接奔着协同去,最后卡在服务端的操作合并上。Univer 的协同协议需要服务端理解每个操作,不是简单转发消息。如果一开始就把所有精力放在协同上,连最基本的“创建表格、保存数据”都还没跑通,很容易误判项目难度。
我当时的做法是:先做单机版本,把工作簿 JSON 完整保存到后端,页面上加一个“保存”按钮。等单机版本稳定运行,再引入实时协同功能。这样做的好处是出了问题可以准确定位:是前端编辑问题,还是网络传输问题,还是服务端存储问题。一上来就做协同,一旦报错,排查范围会非常大。
4.2 版本迭代快,网上找的代码大概率跑不起来
Univer 几乎每隔一段时间就有新版本,接口命名也经常调整。我参考网上旧教程时,遇到过初始化方法不存在、插件名变更、参数从对象改成数组等问题。这不是项目质量差,而是它还在快速演进期。
解决方法是三件套:第一,项目一开始就在package.json里锁住版本号,不要用^自动升级;第二,代码出问题先翻官方迁移文档,不要盲目搜索;第三,紧盯官方仓库的代码示例,很多 API 用法看示例比看文档更准确。这里的核心思路是:Univer 是一个活的项目,不是一套稳定了十年的老框架,心态上要接受变化。
4.3 大数据量场景没做分片,照样卡
Canvas 渲染解决了一部分性能问题,但不代表你可以无限堆数据。如果一次性给 10 万行数据,初始化和公式计算依然会有压力。我的经验是区分“首次加载”和“滚动加载”。首次只给可视区域附近的数据,用户往下滚动后再请求更多行,配合分页或者增量加载,体验会好很多。
还有一点容易被忽略:网格线和公式的实时计算。如果表格特别大,可以把网格线显示关掉,或者把公式计算改成“手动计算”模式,用户点击按钮后才刷新所有公式。对业务系统来说,毫秒级的数据展示远比自动计算更重要。
4.4 不要用 DOM 思维去改样式和单元格
我最初想给某个单元格加背景色和边框,习惯性去找 DOM 节点,结果发现 Univer 里没有“每个单元格对应一个元素”这个说法。正确的做法是通过样式对象去描述,cellData里单元格的值除了v,还可以带s表示样式引用;也可以注册自定义渲染器,在绘制阶段画更多复杂内容。
这件事带给我的教训是:使用 Canvas 渲染的表格库,思路必须转变,要把“界面”想象成“画出来的图”,而不是“一堆元素摆在一起”。想改样式,就去改数据模型中的样式定义;想画图表,就去扩展渲染器。这套模型一旦适应,反而比 DOM 操作更统一。
4.5 打包体积失控,没按需注册插件
Univer 的包很多,如果图省事把官方所有插件都注册进去,打包体积会变得很大。我第一次把所有插件全加上,首屏加载明显变慢,后面改成只注册需要的模块,体积下降很明显。
具体操作:如果你只需要表格,就只注册sheets和sheets-ui,不要注册文档和幻灯片相关的插件;如果不需要公式,可以不注册公式引擎;如果项目首屏追求极致,可以把 Univer 做成动态导入,用户进入表格页时才加载。这个优化逻辑和在业务代码里按需引入组件是一样的,但 Univer 的模块化设计让这件事做起来特别干净。
5. “univer 在线”能怎么落地,我的一点思路
5.1 企业内部数据填报和审批
我看到很多企业内部还在用“把 Excel 传来传去”的流程,收集上来再手工汇总。这种场景非常适合用 Univer 改造成在线版本。业务人员打开一个统一入口,直接在线填数,前端校验格式,提交后数据进入数据库,审批人在后台看到的是同一份实时数据,再也不用担心“谁改了旧文件覆盖了新数据”。
填报表单里有一些细节可以做得很好:下拉选项、单元格数据校验、同行的公式自动计算。Univer 在数据层面已经支持这些能力。把这些能力封装成自己的业务组件后,后台管理员的搭建成本会大幅降低。
5.2 把 Univer 嵌进低代码平台
低代码平台最需要的就是一套可扩展的表格组件。用户在自己的页面画一个表格区域,拖几个字段上去,然后在运行时生成一份工作簿,这种玩法 Univer 天生适配,因为它的数据模型是 JSON 化的,非常容易被低代码引擎编排。
我在做低代码表单时,用 Univer 做了“字段映射表格”,后端模板字段通过表单配置生成表头,前端实例化后填入数据。这个过程中,表单配置和表格数据完全独立,通过 JSON 在中间层转换。后期改表头、加字段,不需要动表格渲染代码,只要改配置结构就行。这个模式对于报表设计器和数据大屏也很适用。
5.3 把业务能力做成插件挂进工具栏
Univer 的插件机制可以让你把业务操作直接挂到工具栏上。例如你做了一个“一键生成报表”功能,可以注册一个自定义按钮,点一下触发后端接口,返回数据后写入表格。用户不需要切换到别的系统,所有操作都在表格里完成。
更进一步,自定义公式函数能带来更强业务能力。比如财务人员直接在表格里写=EVA(2024, "部门A"),底层走扩展函数返回计算结果。这种体验非常接近 Excel 的宏和 VBA,但比 VBA 轻量得多,前端就能跑。企业内很多老员工真正习惯的交互方式就是表格,把业务逻辑“公式化”是降低推广成本的好路子。
5.4 从单机编辑走向真正的“在线协同表格”
如果项目做到一定阶段,需要多人同时编辑一份表格,Univer 前端底层已经留出协同操作的数据结构,但服务端还是要自己设计。一个可落地的分工是:房间服务负责管理参与者,操作队列负责把收到的操作顺序化,操作合并服务解决冲突,存储服务记录完整版本历史。
我建议团队在开发协同前,先明确“冲突结果以谁为准”,不同业务场景接受度完全不同。比如会议室预定表,后提交的人覆盖前面的人就不太合适;但公司通讯录表格,后修改的人可能想直接覆盖。Univer 不会替你决定业务规则,它只负责把数据同步对,规则制定权永远在开发者手上。想清楚这一点,再上协同,效率会高很多。
我个人实际操作下来的体会是,Univer 最香的部分不是 UI 多好看,而是设计上把“数据模型、渲染、UI”拆开了。你可以把它的数据层接进自己的业务,而不是生产一个孤岛表格。最后分享一个小建议:第一次用,不要追求协同和全功能,先在项目里跑一个最小例子,把读写、保存这件事走通。走通了以后,你会发现 univer 在线没想象中复杂,但也没你想的那么无脑,需要后端服务配合的地方,尽早设计好数据结构就行。