说实话第一次知道Univer这个项目是我在给客户做一套内部数据填报系统的时候。当时那个老板要求网页上的Excel最好能多人同时填而且补了一句别用那种弹窗式的老古董控件看着难受。我试过几个方案要么是付费授权黑得吓人要么是导出格式乱七八糟。后来在技术社区里偶然刷到Univer最初只是觉得哦一个开源的Web表格库结果真部署试了一把之后我决定把整个协同办公的底层都换成它。这篇东西不打算写成官方文档的翻译版而是想以一个真正把它跑进生产环境、接过需求、踩过坑的开发者视角聊聊Univer到底好在哪里、难在哪里、坑又在哪里。如果你正准备选型Web端的表格组件、或者你手里已经有一堆Excel残留的痛点这篇应该能帮你在动手前省下不少弯路。1. 为什么我要把业务系统重写到一个网页表格上1.1 被老Excel体系卡住脖子的真实场景我们之前做的数据填报系统本质上就是在线Excel 审批流的结合体。前端用的是某老牌表格控件后端生成一个xls文件用户下载、填完、再上传服务器解析之后写入数据库。听起来很完整对吧但实际运维起来你会发现用户根本不会按你规定的模板填。有人删除列有人加备注有人把数字填成文本有人用WPS打开后再保存格式就变了。最离谱的一次有个同事把公式粘贴了整整一列结果解析程序直接内存溢出把服务器搞挂了。这就是老式文件流转方案的通病你不是在管理数据你是在陪用户玩格式捉迷藏。所以我开始思考一个更本质的方向能不能把填表这件事直接变成在网页里操作表格用户在浏览器里直接编辑数据结构始终是前后端共用的JSON不落文件、不转格式、不出错。而Univer正好提供了一个接近原生Excel体验的Web端表格引擎还自带协同能力。1.2 Univer是什么它在整个体系里处于哪一层Univer是一个开源、基于TypeScript的Web办公套件。它覆盖的不仅仅是表格Sheet还包括文档Doc和幻灯片Slide目前社区里用得最多的还是Sheet模块。它做的事情简单说就是在浏览器里画出一套完整的电子表格界面同时提供数据存储、公式计算、渲染、编辑和协同的能力。从架构上说它分成了层层解耦的部分核心层Core负责命令、数据模型、协同能力的底层建设。渲染层Render基于Canvas绘制单元格和工作表保证大数据量下的交互流畅度。UI层提供工具栏、右键菜单、公式栏这些和用户直接打交道的界面。插件层Plugin通过独立插件扩展功能比如接入AI、接自定义菜单、接数据校验。我自己理解它本质上是一个可编程的Excel内核。你不需要关心单元格怎么画、公式怎么算只需要把数据喂给它然后听它的事件、调它的命令。1.3 跟其他方案横向比一比选型的时候我前后比较过几类东西第一类重编辑器型比如OnlyOffice、LibreOffice Online。功能确实全面但部署成本高、集成深度低你要塞进自己的业务系统里得像伺候一个独立应用一样伺候它。第二类纯表格组件型像Handsontable、AG Grid。它们表格能力强但公式、样式、合并单元格这些Excel级别交互比较弱。第三类国内商业SDK功能强大但授权费不低代码封闭出了问题只能等官方。第四类Univer开源、架构现代、基础体验到位且支持协同。虽然像打印、更多复杂函数、移动端适配还在完善中但对于深度嵌入业务系统这个场景它反而最顺手。这里多提一句如果你只是想要一个快速展示数据的表格AG Grid那类组件会更轻但如果你要的是用户在网页里像用Excel一样操作数据Univer的起步形态就更对路。2. 跑起一个Univer表格从集成到数据交互的核心逻辑2.1 最简接入Demo五分钟看到表格我第一次接入Univer是在Vue3项目里社区对React和Vue都有对应的包。按官方Quick Start走一遍核心依赖就这几项npm install univerjs/core univerjs/sheets univerjs/ui univerjs/sheets-ui univerjs/sheets-formula univerjs/sheets-numfmt我当时的初始化代码长这样Vue3的封装思路React同理import { Univer } from univerjs/core; import { defaultTheme } from univerjs/theme; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; const univer new Univer({ theme: defaultTheme, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin, { container: app, layout: { toolbarConfig: { undo: true, redo: true, font: true, align: true, }, }, });注意这里的UniverSheetsUIPlugin里的container参数需要的是一个DOM节点的id字符串。我第一次写成了传入元素本身结果界面没渲染出来控制台也不报错查了半天文档才发现是这个坑。跑起来之后界面长得很清爽默认自带一个空白工作表。用户可以直接输入、调字体、合并单元格、拖拽填充——这一套基础交互已经做得很像原生Excel了。2.2 Univer的数据模型一切皆命令命令即数据如果你只是把它当展示组件用那就亏了。Univer最核心的设计理念是所有用户操作都会转化成命令Command命令被推入命令队列执行同时产生数据变更。这就让撤销/重做和协同同步共用一套机制也让你可以做程序化操控。比如我想通过代码给 A1 单元格写入hello可以用Command方式import { CommandType, ICommandService } from univerjs/core; import { SetRangeValuesMutation } from univerjs/sheets; const commandService univer.__getInjector().get(ICommandService); commandService.executeCommand(SetRangeValuesMutation.id, { unitId: sheet-001, workbookId: sheet-001, worksheetId: sheet-001, cellValue: { 0: { 0: { v: hello }, }, }, });这套路径看起来有点绕但它的好处是你对数据的每次改动都是可追溯、可回放、可协同广播的。就好比一个记事本应用你的每一次按键都被记录成事件流而不是只保存最终文本——后者的缺陷是多人同时操作时不知道谁改了什么。我后来做需求的时候就形成了一条经验不要直接去改Univer内部的workbook数据而是尽量发Command。虽然直接改对象也能生效但绕过命令机制会导致撤销栈错乱、协同不同步。这个坑我踩过后文细说。2.3 公式、格式与图表的实测结论Univer的公式引擎是独立实现的支持不少常用函数比如SUM、VLOOKUP、IF这些。我用它跑过一线的业务报表几十个单元格联动计算没什么压力。但也要承认如果涉及特别复杂的跨簿引用、动态数组或者老旧的特殊函数它可能还未完全覆盖需要你自己扩展。格式这块Univer支持数字格式、条件格式、边框、底纹基本键盘上的格式化操作都能做。我自己用的场景里最常用的是numfmt插件它能把单元格设置成保留两位小数、百分比、日期格式等。这个能力在做业务填报时非常关键因为用户习惯了Excel的格式习惯你要让他在网页里也能设置一样的显示效果。图表属于加分项目前Univer Sheet支持了柱状图、折线图等基础图表插入后可以调整数据源。跟Excel原版图表比配置项还没那么丰富但胜在融在表格里可以直接看。我在项目里的做法是如果客户需要复杂的商业图表我不让Univer直接画而是拿到范围数据交给ECharts去渲染两边通过事件联动这样体验反而更稳定。2.4 如果你想把它嵌进自己的后端系统Univer本身是纯前端方案数据层需要你提供一套存储。最常做的有两种方案A全量JSON同步。每次操作后用ICommandService拿到最新workbook数据整体存到后端。优点是简单可靠缺点是大表同步流量不小。方案B操作日志回放。前端把Command序列发送到后端后端追加保存再同步给其他客户端。这其实就是协同编辑的基础版占用空间小但要求你处理命令的合并和顺序。我自己一开始选了方案A后来为了做协同改成方案B才发现Univer这套命令架构的价值比想象中大得多。3. 协同编辑从一个假需求变成一个真核心3.1 协同到底解决的是什么问题客户说多人同时填写的时候第一个想法当然是让两个人同时打开表格一个看A区一个填B区。但真正难的是冲突处理如果两个人都改了同一个单元格保留谁的Excel的老答案是最后保存的人赢但企业的现实需求是合并两个人的意图别丢数据。Univer官方提供了协同方案但本质思路很清楚本地执行命令执行后把操作以命令形式广播给远端远端按同样的命令队列依次执行。这种模型下处理的是操作序列的一致性问题而不是最终数据的快照同步。就好比两个人在一块白板前讨论每个人都把自己写的字贴上去先来后到都会保留而不是我抢走你的笔最后写一行字。注意Univer协同方案目前比较适合局域网或者可靠的网络环境。如果你的客户需要公网跨地域协同建议在服务端加上可靠的消息队列和命令顺序保证不能简单依赖WebSocket的到达顺序。3.2 我把协同服务端拆成了三个角色刚开始照着官方文档捣鼓的时候很容易被协同二字吓住。后来我把整体拆成了三块思路立刻清晰了前端命令采集拦截Univer的Command执行事件将新增命令发送到服务端。服务端顺序化维护一个自增的操作ID所有客户端提交的命令由服务端排队编号按编号广播。客户端命令应用收到服务端广播后如果命令来源是别人就在本地的Univer实例上执行对应Command如果是自己发出的则跳过避免重复执行。这里最关键的细节是命令本身要无视来源地执行。也就是说不管是本地用户改的还是远端用户改的执行的逻辑完全一致。这样的好处是你不用为谁改的写两套逻辑服务端也只用转发不需要理解命令内容复杂度一下子降下来了。实际落地时我用的是Node.js NestJS Socket.IO命令消息长这样{ id: 1023, command: { type: mutation, id: SetRangeValuesMutation, params: { unitId: sheet-001, cellValue: { 2: { 5: { v: 已审核 } } } } } }服务端拿到后追加一个id然后广播给同房间的其他客户端。客户端解析到SetRangeValuesMutation后直接执行。这个流程我跑了一个月基本稳定。3.3 协同踩过的三个真实问题第一个问题本地操作后不应再执行远端广播的同一个命令。这个比较好解决Univer执行命令时会有一个消息标识我判断来源即可跳过。第二个问题Univer命令有些是瞬时型的有些是聚合型的。比如一个拖动填充操作实际会连续执行几十条子命令你不会想让服务端每秒广播一百条消息。我的办法是把一个操作周期内的命令按时间窗口合并成一条批处理命令再发给服务端。试下来网络压力下降了一个数量级。第三个问题光标位置同步。协同编辑时A用户正在看哪一行、选中的区域是什么B用户最好也能看到否则填错了都不知道是谁在动。Univer的光标同步需要单独实现我通过派发SelectionChange事件把用户的光标区域发给服务端再广播给其他端。这个功能做完了用户才会真正觉得我们是在同一张表上干活。4. 插件系统把一个表格库变成一张业务白纸4.1 Univer插件到底是怎么注册和触发的我第二篇文章里专门写过Univer的依赖注入设计。它整个容器是一个轻量的依赖注入DI容器你注册的每一个插件都会被当作一个可注入的服务。插件可以声明自己依赖哪些核心服务Univer在初始化时自动把它们装配好。我理解这种设计的一个好处各功能模块之间不通过全局变量互相引用而是通过插件容器按需获取服务。比如我写了个天气数据自动填充插件它不直接操作表格UI而是注册一个命令通过命令调SetRangeValuesMutation填数据。UI层、核心层、插件层分开之后乐意怎么扩展都不会污染主流程。4.2 亲手写一个成本估算插件为了演示我写过一个给销售部用的成本利润率估算插件。需求是选中B2到B10这些数量列点击工具栏的按成本利润估算自动在下一列生成估算结果。插件结构如下import { Plugin, CommandType } from univerjs/core; export class CostEstimatePlugin extends Plugin { static override type costEstimate; override onStarting(injector: Injector): void { this._registerCommands(injector); } private _registerCommands(injector: Injector) { const commandService injector.get(ICommandService); commandService.registerCommand({ id: estimate-cost, type: CommandType.COMMAND, handler: async (accessor) { const selection accessor.get(ISelectionService).getCurrentSelection(); // 拿到选中的单元格计算利润然后执行 mutation }, }); } }注册之后还需要把命令挂到工具栏上。Univer UI插件的工具栏配置支持自定义项我用toolbarConfig.customMenu往里面塞了一个按钮点击后执行estimate-cost命令。整个过程没有改一行核心源码这也是我认为Univer值得选的一个重要理由——业务方的个性化需求永远改不完插件隔离能让你少背很多改坏主流程的黑锅。4.3 菜单、右键菜单和自定义弹窗的挂载思路除了工具栏Univer也提供了右键菜单的扩展点。我常用的是在单元格右键菜单里加备注审批这样的操作。做法上通过MenuConfig或直接操作IMenuService注册一个MenuItem绑定到onClick在点击后弹出自定义的Modal或者侧边栏面板。这里有个小提醒Univer的弹层组件是基于它自己的UI系统渲染的你用普通HTML/CSS也能做但为了样式统一建议优先复用它的Tooltip、Modal、Sidebar组件。如果你直接用第三方组件库很容易出现层级错乱、样式冲突的问题。因为我试过用Element Plus的Dialog直接盖在Univer上结果弹窗盖不住它的下拉框原因就是两个库的z-index体系互相打架最后还是老老实实切成Univer自带弹层世界清净了。5. 大数据量渲染与性能调优一次真实的崩溃排查5.1 为什么是Canvas而不是DOM表格Univer的性能底气很大程度来自它的渲染层用的是Canvas。传统DOM表格单元格一多浏览器就得创建成千上万个节点你滚动一下浏览器都得崩溃。Canvas的思路是只用一块画布根据当前可视区域动态绘制可见的单元格滚动时重绘。你可以把它理解成玩一个开放世界游戏游戏引擎不会把整个地图一次性加载到显存里而是只加载你镜头周围的一片区域。Univer的渲染也是这个逻辑因此它支持几十万行数据的基础滚动查看不会像DOM表格那样卡成PPT。5.2 30000行数据的实测卡顿点我拿一张3万行、8列的实际业务表做过压测。纯滚动浏览流畅度没问题输入筛选条件、点击排序响应也在可接受范围内。真正卡的地方是一次性设置大量单元格的值。比如我后端返回一整批数据直接循环构造cellValue然后发一个大命令渲染层会把这个大命令拆成多次绘制但是首次绘制的时间会非常长界面会白屏一两秒。我的解决方案是分批提交或在表格显示前先隐藏渲染层数据set完再显示这样用户感知不到中间的白屏。另一个卡顿点是格式太多。如果你给整列都设置数字格式、条件格式即使单元格数据没变渲染层每次都要重新计算这些格式状态性能会下降。我的经验是数据多的时候只在表头和关键列设置格式正文区尽量克制使用样式。5.3 懒加载、虚拟滚动之外的优化思路Univer本身做了虚拟滚动但这不意味着你不需要管数据量。我在实际项目里会额外做这几层优化按需加载字段后端接口只返回当前筛选条件下用得到的字段而不是整张大宽表。数据分区加载默认只加载前500行用户滚动到底部时再请求下一页。因为我给一个报表系统做过一个9000行的明细表全部加载后协同同步数据太大滚动倒还好但每次保存全量数据能把数据库写盘带宽吃满。防抖保存用户编辑停止5秒后再自动保存避免每敲一个数字就触发一次全量同步。这些优化跟Univer本身无关但在业务系统里叠加使用效果非常明显。6. 生产环境踩坑清单从样式到权限的细节问题6.1 为什么导出的文件要用服务端转换而不是前端生成Univer的导出功能还处于半成熟阶段。前端可以实现导出XLSX但遇到复杂的合并单元格、样式缩放、公式缓存时导出的文件偶尔会和原生Excel表现不一致。最稳妥的方案是前端把workbook数据JSON传给后端后端用ExcelJS或者LibreOffice命令行转成最终文件。我印象最深的一次事故客户用WPS打开导出文件发现单元格背景色乱了查下来发现是前端导出时少写了一个样式属性。后来改成后端统一转换再也没出过类似问题。6.2 权限控制不要只靠前端菜单隐藏因为Univer是网页应用它的工具栏按钮项是可以动态配置的。当时老板说财务的人才能看到导出按钮我第一反应是前端配置一下就行。后来细想不对只要功能逻辑在前端就有被绕过或篡改的可能。我的做法是后端给每个会话下发权限数组包含allowEdit、allowExport、allowViewFormula等字段。前端根据权限动态初始化工具栏和右键菜单。同时在命令执行层做一遍权限校验——即使恶意用户伪造命令后端也会拒绝真正影响数据的操作。简单说前端管体验后端管安全。6.3 样式与回车的习惯差异中文用户录入数据时常常会在单元格里直接按回车换行。Excel的默认行为是回车会跳到下一个单元格换行需要用AltEnter。Univer的默认行为同样遵循这个逻辑。但实际接需求时有很多习惯了WPS或者某些国产在线表格系统的用户并不知道要按AltEnter于是打了一堆回车后发现光标跳到别的格子里了就以为出bug了。我在项目里加了一个设置开关让用户可以在回车是跳下一格和回车是换行之间切换还在设置面板里放了提示文案。这种细节看起来小但实际上决定了业务方愿不愿意从Excel迁移过来。6.4 移动端适配的现实Univer官方对移动端的支持还在完善中但我遇到过客户临时用iPad填报的需求。实测下来触屏基础操作能做但是工具栏太多按钮会挤在一起下拉滚动也不如桌面端跟手。我的建议是如果业务强依赖移动端不要指望Univer一步到位要么你封一层响应式布局把工具栏精简到只保留必要的操作按钮要么在移动端场景里退回用普通表单组件收集数据只在PC端使用Univer做复杂表格编辑。这样分工明确两边体验都不会差。7. 什么情况下我会劝你别用Univer讲了这么多好话我也要说点劝退的话。Univer不是万能的如果你符合下面几种情况选型时要慎重团队没有前端基建能力。Univer虽然API清晰但毕竟是框架级的东西你需要有人能啃源码。如果你只是希望找个表格控件装上去就完事那维护成本可能会超出预期。需要高度兼容Excel高级功能。比如宏、数组公式的极端用法、复杂的图表透视Univer目前肯定做不到100%还原。如果客户要求务必和Excel打开一模一样那现阶段更适合用商业方案。项目周期特别紧。Univer的生态还在快速迭代中接口偶尔会有breaking change升级大版本时你需要预留适配时间。我记得官方一夜之间把初始化方式从new UniverSheet()改成new Univer()注册插件的方式老代码迁移起来非常费劲。需要离线编辑。Univer是一个在线优先的框架如果你要的是完全离线环境下还能顺畅编辑、事后同步的体验那它的协同架构还需要你自己做很多工作。反过来如果你的诉求是在业务系统里嵌一个强交互、可扩展、未来能做协同的在线表格那Univer确实值得押注。写在最后我从最开始只是想在页面上做个好看一点的Excel到后来把它做成了整套业务数据协作的中枢整个过程最大的心得其实是一个好的底层架构能让你在遇到复杂需求时还有得选。Univer是不是最好的Web表格引擎我不敢下定论但它的命令模式、依赖注入、插件体系确实让我在接需求时很少因为底层受限而改方案。如果你也准备上车我给三个建议第一先花三天时间把官方的架构文档和Demo源码读透尤其是命令和Plugin这两块别急着堆功能。第二把Univer当成你业务系统的一个引擎所有数据输入输出都走它的Command体系别贪图方便直改内部对象。第三协同功能尽量从第一天就规划好哪怕你现在只做单机编辑也要把操作日志、命令记录、版本号这些字段预留上不然以后想加协同数据层要动刀子的地方非常多。最后送一个小技巧遇到渲染或者协同的诡异问题先用官方仓库的issue搜索一遍经常能看到维护者的回复。它是一个活项目比很多文档完备但几年不更新的老牌库要靠谱得多。
阅读完成 · 觉得有帮助?