
如果你最近在调研开源在线表格方案应该会频繁从技术社区的讨论里看到 Univer 这个名字。“univer在线”相关话题的热度也在涨很多人想知道它和我们熟知的 Luckysheet、x-spreadsheet、SheetJS 到底有什么不同。我先说结论Univer 不是又一个做 Demo 展示的表格组件而更像是一套面向 Web 的 Office 基座它想把表格、文档、幻灯片这几种能力统一成可嵌入的 SDK让任何前端应用都能快速变成“能打开文件的应用”。这篇文章我会从实际接入和使用经验出发聊聊为什么大家开始关注 Univer、它的架构逻辑是什么、怎么把它嵌入到现有 Web 项目里以及我在接入过程中踩过的一些坑。无论你只是技术选型阶段来看看还是已经准备把它用于内部系统这篇内容应该都能帮上忙。1. Univer 在解决什么问题1.1 它不只是一个“在线表格”很多业务系统都需要“像 Excel 一样的操作体验”。员工填月度预算、运维录入设备台账、运营维护广告排期这些场景如果只用传统的 HTML table很快就会被各种需求打败需要冻结首行首列、需要下拉筛选、需要数据校验、需要条件格式、需要跨表引用公式、需要多人同时打开且不互相覆盖。自己从零写一套这些能力说实话至少是按月甚至按年来算的工作量。Univer 的思路是把工作簿Workbook、工作表Sheet、单元格Cell作为一级数据模型把这些 Excel 世界的核心概念搬到 Web 上同时提供一套完整的数据读写 API。本质上它做的不是“画一个表格”而是“复刻一套电子表格引擎”。我举个实际场景。一个企业内部系统要做季度预算填报表单上最复杂的部分往往不在提交页面而是在那个“预算明细表”里。用户要在里面添加行、合并单元格、做行小计、引用上一个 Sheet 的数据最后还要导出 Excel 发给财务。这种需求如果用低代码平台的表格组件往往撑不住用自研表格又要和产品经理反复确认“这个功能这期能不能不做”。而 Univer 这类组件的好处是直接给用户一个接近 Excel 的操作界面你的系统只是它的宿主。1.2 相比其它开源方案Univer 凭什么出圈在线表格开源领域其实已经有过几轮探索老牌的有 x-spreadsheet、Luckysheet商业一点的像 SpreadJS、Handsontable。和它们相比Univer 让我觉得值得投入时间关注的原因有这么几个一是模块划分清晰。它把核心、UI、Sheet、公式、协同等拆分成了不同的包。你不用全量引入所有功能只接入表格能力时可以避免加载一堆用不到的代码。这种思路对包体积敏感的业务场景很重要。二是渲染层选择了 Canvas。这一点后面会详细说简单讲就是当数据量很大时基于 DOM 的表格方案会因为节点太多而卡顿Canvas 渲染天然更适合高频滚动和区域重绘。三是协同被纳入了体系设计。传统做法是先做单机表格再补协同往往协同部分很生硬。Univer 从协议层就开始考虑状态同步虽然协同服务器不是零成本但至少它不是“以后再说”的补丁方案。四是有商业公司在持续维护。开源项目最怕的是作者不更新、贡献者流失。Univer 背后有团队在推进发布节奏和文档建设都比较正常只看 GitHub 上的更新频率和 issue 回复速度能感觉到它还在快速增长期。当然我不能只夸不吐槽。Univer 给我的首要印象是“年轻”API 还在频繁变化像createWorkbook、createSheet、FUniver这些方法的形态在不同小版本里就可能不同。社区中文资料也还没到“搜一个踩坑问题就有答案”的程度很多问题需要自己去源码里翻。1.3 适合谁来用什么场景先用它如果你属于下面几类人Univer 很值得列入备选清单前端开发你正在做数据中台、低代码平台、ERP、项目管理这类系统需要给产品加上一个“能填、能算、能导出”的复杂表格。技术选型负责人你对比过 Handsontable 等商业授权方案希望有一个开源许可更宽松、可定制性更强的方案。想私有化部署在线办公能力的团队Univer 的 Server 端能力可以做内网协同数据自持。纯粹对表格渲染、协同编辑算法感兴趣的开发者它的源码也是很好的学习材料。不太适用的场景也有如果你的需求只是展示几十行只读数据用 Canvas 渲染表格属于大炮打蚊子如果你需要非常完整的 Excel 文件兼容性包括图表、宏、复杂透视表这些能力目前还没有哪个开源项目能打包票。2. Univer 的技术架构几个关键设计决策2.1 从“核心 插件 命令”看 Univer 的骨架我第一次看 Univer 文档的时候有个感觉它不像是普通组件库而更像一个“浏览器里的操作系统”。在 Univer 里univerjs/core管的是底层核心模型和骨架不带 UI也不带表格逻辑真正让界面可交互的univerjs/ui、让表格出现的univerjs/sheets、让公式跑起来的公式插件都是通过“注册插件”的方式挂到核心实例上的。这种架构和很多人熟悉的 Vue/React 组件库思路完全不一样。组件库是你调它它给你返回组件Univer 是你先创建运行时再往运行时里安装能力。好处是扩展能力非常强团队可以针对自己的业务写插件比如在右键菜单里加一个“一键合并同类项”这个按钮对应的动作可能是一段自定义命令再通过注册命令的方式接入系统。命令模式也是 Univer 让我比较舒服的设计。简单理解就是所有用户操作都被封装成 Command命令里定义了“执行什么”和“撤销时做什么”。这和 Redux 的 action 思路类似集中了操作入口也让协同同步变得简单因为需要广播给其他客户端的本质上是这些命令操作而不是一串零散的属性变更。2.2 为什么 Univer 选择用 Canvas 渲染表格在线表格早期方案里很多是用 DOM 来渲染每一个单元格。数据量小的时候没问题但是一旦表格有上万行浏览器创建的 div 数量会迅速膨胀滚动时整个页面都会变卡。Univer 的渲染层用 Canvas 绘制只把可视区域的内容画出来滚动时重新绘制当前视口里的数据。这是它在大数据量场景下还能保持相对流畅的主要原因。但它也为 Canvas 渲染付出了一些代价。因为不在 DOM 上画单元格你不能简单通过 CSS 给某个单元格加一个特殊样式比如“把第三行背景变红”。要达成这种效果你得用 Univer 提供的数据模型字段比如在单元格数据里设置背景色或者使用自定义渲染器。这个学习曲线是需要提前知道的。另外一个我印象很深的点是Univer 把渲染拆成了多个图层比如选区图层、内容图层、冻结行列图层。这样做的好处是局部更新时只需要重绘对应的图层不用每次全量刷新 Canvas。这也是它在频繁拖动选区、实时显示行列高亮时感觉比较顺手的原因。2.3 公式引擎不是“先算完再填上”那么简单公式是电子表格的核心。Univer 的公式引擎不是每个单元格独立算一遍而是维护了一个依赖计算链。比如 A1 单元格填了B1C1B1 变了以后计算链会判断只有 A1 需要重新计算不会导致整个 Sheet 全都重算一遍。这个机制对性能的影响很实际尤其在业务报表里到处都有连锁引用的情况下。公式引擎还区分了“存储值”和“显示值”。A1 存的是一个公式字符串但页面上显示的是公式计算出来的结果。在监听数据变化、做条件格式判断时需要注意拿到的是哪种值否则容易出现“表格里显示是 100我读出来却是一段公式”的尴尬。目前 Univer 的函数覆盖量已经比较可观支持跨表引用、数组公式、条件统计等常用能力。对普通业务系统来说日常 Excel 操作基本够用。如果业务里有大量 VBA 宏逻辑那无论哪个开源方案都会和你预期有差距。2.4 协同编辑是怎么设计的如果你只是把一个 Univer 表格嵌到页面里不开启协同那其实用不到协同服务器。Univer 的单机模式就是一个普通的前端组件。但需要多人同时编辑同一份数据时Univer 的思路是把客户端操作生成 operation实时同步给协同服务端由服务端合并冲突后广播给所有客户端。协同开发里有两个经典路线一个是 OTOperational Transformation一个是 CRDT。Univer 的协同协议并不是简单的乐观锁而是基于命令同步来做合并。这样做的好处是操作语义明确比如“把第 6 行行高改为 40”和“在第 3 行后面插入一行”是语义清晰的命令冲突时更容易判断谁先谁后。真正要在生产环境上协同我的建议是不要自己从头去写服务端优先调研官方协同服务或社区维护的 server 端实现。协同看着简单实际上有大量边界情况比如断线重连后的数据补发、WebSocket 拓扑变化、并发光标位置矫正。这些自己做很消耗精力用好现成组件才是正道。3. 把 Univer 接入 Web 应用从安装到跑通3.1 初始化工程前需要准备的三件事在动手之前有几个前提条件先确认清楚。第一Univer 对前端工程有要求。如果你的项目还在用老旧的 Webpack 4 配置且不打算升级可能会在依赖处理上花很多时间。建议使用较新的 Vite 或 Webpack 5Node 版本保持 18 以上这样遇到问题的概率会小很多。第二Univer 不是“一把梭”的单个组件包它的包名非常多。建议先走官方的 Preset 预设路径不要一开始就手动组装各种插件。手动组装虽然灵活但需要在注册顺序上踩不少坑初学阶段没有意义。第三容器元素必须要有确定宽高。我见过太多初始化后白屏的案例多数不是代码写错而是外层容器height: 0。Univer 绘制 Canvas 时依赖容器尺寸所以先在 HTML 里写好一个带高度和宽度的 div比如 800 x 600再接初始化代码。使用 Vite 建一个最简工程的话大概是这样npm create vitelatest univer-demo -- --template vue-ts cd univer-demo npm install然后把后面要装的 Univer 相关包都装好。3.2 使用预设包快速创建一个最小表格实例Univer 的 Preset 方案是我目前最推荐给新手的路径。它相当于把常用插件组合好了你只需要传配置项。一个最小示例大致是import { Univer } from univerjs/presets; import { UniverPresetSheets } from univerjs/preset-sheets; import univerjs/presets/lib/styles.css; const univer new Univer( UniverPresetSheets({ container: document.getElementById(app)!, locale: zhCN, }) );这里的UniverPresetSheets会自动注册一系列和 Sheet 相关的核心插件包括 UI、公式、筛选、条件格式等。根据我实际使用的感受对于大多数内部系统场景这个预设组合已经很够用不需要手动逐件去安装。如果你使用的是旧版 API也可能会看到类似的实现方式import { Univer } from univerjs/core; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverUIPlugin } from univerjs/ui; const univer new Univer({ locale: zhCN, }); univer.registerPlugin(UniverUIPlugin, { container: document.getElementById(app)!, }); univer.registerPlugin(UniverSheetsPlugin);两种方式都能跑通区别只在于 Preset 帮你做了封装。考虑到 Univer 版本迭代很快我更建议以官方当前文档为准本文的代码只作为理解思路的参考。3.3 创建 Sheet 并写入单元格数据表格初始化之后第一件想做的事通常是写入数据。Univer 推荐通过 Facade API对外门面 API来操作也就是FUniver。它可以看成是让你不用操作底层复杂模型的一层友好封装。下面这段代码演示了如何获取当前活动工作表并写入两个单元格import { FUniver } from univerjs/presets; const univerAPI FUniver.newAPI(univer); const workbook univerAPI.getActiveWorkbook(); const sheet workbook?.getActiveSheet(); sheet?.setValue(0, 0, 项目); sheet?.setValue(0, 1, 100); sheet?.setValue(1, 0, 实际值); sheet?.setValue(1, 1, 86);注意这里的行列索引都从 0 开始如果你是从 Excel 转过来的用户很容易以为第一行是行号 1实际 API 里的 (0, 0) 才是 A1。如果你已经有一批 JSON 数据要灌入表格最简单的方案是循环setValue但数据量较大时这并不高效。更合理的做法是一次性构建好工作簿数据在创建 Univer 实例时通过配置传进去让初始化渲染只跑一次。这样能明显加快首屏显示速度。批量写入的代码通常长得像这样示意const initialData { sheets: { sheet1: { name: 销售台账, celldata: [ { r: 0, c: 0, v: 品类 }, { r: 0, c: 1, v: 数量 }, { r: 1, c: 0, v: 手机 }, { r: 1, c: 1, v: 280 }, ], }, }, };不同版本的字段结构可能有细微差别但思路是一致的在初始化前就把数据模型准备好而不是初始化后再逐格写入。3.4 导入导出 Excel 的接法在线表格系统基本都会遇到“导入 Excel 文件”和“导出 Excel 文件”的需求。Univer 官方在导入导出方面也提供了插件但我实际操作下来有个感受如果只是要求格式不复杂的数据导入最稳定的是用 xlsx.js 这类通用库先解析 Excel再把数据转换成 Univer 的 Sheet 数据格式通过 Facade API 写入。这样既不用在 Univer 的导入逻辑上卡太久也能复用团队里已有的 Excel 解析经验。导出方向也同理。虽然 Univer 支持导出但如果你只导出某一个 Sheet 的表格区域自己生成一份新的工作簿并交给前端下载会更可控。这里有个建议导出前一定要和业务方确认“哪些格式是必须保留的”。因为 Excel 里有大量格式元素是任何开源组件都难以完整还原的比如图表联动、数据透视表、宏脚本。提前在需求阶段对齐边界能避免后面被测试提一长串兼容性问题。4. 进阶实操工具栏配置、权限控制、协同与性能4.1 工具栏与功能区配置Univer 的工具栏默认包含了很多菜单比如文件操作、撤销重做、字体、对齐、筛选、数据透视等。对于业务系统来说默认工具栏不一定是产品想要的。例如财务系统只希望用户填数据不希望他们乱点“导入导出”导致格式变化就需要把工具栏收窄。使用 Preset 配置时可以按官方文档传入自定义的 toolbar 配置。你可以保留需要的一级菜单比如“撤销”“重做”“筛选”“合并单元格”隐藏那些和业务无关的能力。这个过程本质上是配置项驱动的不需要改源码关键点在文档里对应“toolbar”字段。自己做自定义菜单按钮也是常见需求。流程一般是先在 UI 插件里注册一个按钮把按钮点击和自定义 Command 绑定起来然后在 Command 里操作 Univer API比如读取选中区域、执行汇总计算、把结果写回某个单元格。这么一套流程其实是 Univer 扩展能力最值得学习的部分想深度定制产品的同学可以从这里入手。4.2 只读和资源权限的设置思路很多系统里表格不是人人可编辑的。比如供领导查看的仪表盘数据只提供只读展示供运营填写的排期表只允许指定人修改。Univer 在权限模型上提供了相关支持你可以配置单元格锁定、工作表只读等属性。但实现上我建议采取“双重校验”思路。前端的权限设置比如隐藏编辑按钮、让单元格不可编辑只是体验层面的优化。真正可靠的权限控制必须放在后端当用户提交一个修改操作时服务端要校验该用户是否拥有该操作对应资源的编辑权限。因为数据模型在客户端是可以被绕过甚至被恶意构造的不能单纯相信前端不可编辑状态。Univer 的命令模式在这里帮了大忙。因为每个操作都是命令你可以把命令报给后端鉴权后端校验不通过就直接拒绝并回滚。这种设计在协同场景里尤为重要因为多人并发操作时总不能靠“前端按钮灰掉”来防止冲突。4.3 多人协同的部署与注意事项Univer 的协同能力不是零部署的。单机情况下每个用户打开的是各自本地实例互相之间没有实时同步。要让多个用户编辑同一份表格需要有一个协同服务端负责集中处理命令广播和状态合并。我建议做协同之前先想清楚三个问题实时性要求有多高、并发人数大概是几十还是几千、是否需要保存完整的历史版本记录。这些问题的答案会直接决定选用什么方案。如果只是一个小团队内部使用官方协同服务可能就够如果是面向几千人互联网应用那你得先评估服务端能力能不能扛住高频命令推送。协同还有一个容易被忽略的问题光标和选中态同步。多人同时编辑时每个人都能看到其他人的光标位置会提升协作体验但实现上需要额外的消息协议处理不好会产生明显延迟和闪烁。在做产品演示时我一般会先把无关的配置项去掉确保现场不翻车。4.4 大数据量表格的性能调优“我加载了一万行表格很卡怎么办”是表格类组件绕不开的话题。用 Univer 时我总结了几条有效优化手段。第一避免一次性灌入全部数据。如果你可以从接口分批拉取就按滚动位置动态加载。配合 Canvas 按视口渲染效果会好很多。第二大批量写入数据时使用批量 API不要用循环调用setValue。每调一次 API 都可能触发一次渲染调度。循环一万次等于触发一万次重绘再好的渲染引擎也扛不住。第三公式重算要控制范围。实时公式计算在数据量上来后会成为主要性能瓶颈。可以考虑把“自动重算”调整为“手动重算”或者尽量将公式引用范围限制在当前可见区域避免让一个大 Sheet 的数据全部进入依赖链。第四销毁页面时记得释放资源。如果应用是单页应用频繁切换路由会创建多个 Univer 实例不释放会造成内存持续增长表现为浏览器越用越卡。这个点很容易被忽略因为本地测试时你看不出问题只有在一个长期运行的 Web 应用里才会慢慢恶化。5. 踩坑记录接入 Univer 最容易翻车的几个点5.1 初始化白屏白屏是我见过最多的问题而且大部分时候都不是代码逻辑问题。我以前排查过一个案例页面里 Univer 实例创建成功了控制台也不报错但界面上就是什么都没有。最后发现是外层容器用了 flex 布局且没有设置高度导致 Canvas 的父级高度为 0Univer 绘制不出来。遇到白屏优先级最高的检查容器是否设置了width和height。其次检查样式文件是否已引入。如果你用的是 Preset 包记住要导入对应的 CSS。最后检查浏览器控制台是否有异常输出Univer 插件注册顺序错乱有时也会静默失败。5.2 API 版本变化引起的老代码失效Univer 的 API 变动频率在 0.x 阶段是比较高的官方也在不断打磨。比如早期版本里可能使用createWorkbook之类的方法后来的版本就统一为通过实例 Facade API 访问。这意味着网上的老教程、老示例代码很可能在自己的项目里不能直接跑通。应对办法我总结下来就两个一个是锁定版本号无论是写在 package.json 里还是通过 lock 文件锁住都不要轻易升级小版本另一个是把 Univer 相关操作封装成独立服务模块比如univerService.ts这样底层 API 变了只需要改这一个文件不会全项目翻。从团队协作角度我要专门提醒一下每次升级前先看 changelog重点搜索带有 “breaking change” 和 “migration guide” 字样的内容。不要凭感觉升级否则同一时间提交代码的人会集体困惑。5.3 自定义样式和组件覆盖因为 Univer 用 Canvas 渲染表格主体想通过 CSS 去改单元格样式往往走不通。例如你给.cell写个背景色大概率不会生效因为 DOM 里根本没有这些元素。Univer 的正确做法是在数据模型层面设置样式或者在自定义渲染器里绘制内容。也就是说想要让某一列数字显示成红色应该在数据写入时给单元格加上textColor之类的字段而不是去写一条 CSS 规则。另一个常见的坑是在表格外层包了一个带有transform的容器比如做缩放、位移动画会导致 Canvas 内部的事件坐标错位点击单元格时可能选中错误的区域。遇到这类问题要么去掉 transform要么根据 transform 反向修正事件坐标。5.4 多实例和内存释放问题清单如果你在一个页面上同时创建多个 Univer 实例比如两个并排展示的 Sheet内存开销不是简单翻倍。每个实例都有独立的命令队列、渲染上下文、事件监听和公式依赖链使用资源调度时要小心。下面这张表是我复盘时整理的常见现象和对应处理思路供参考现象可能原因处理方式切换路由后页面变卡旧实例未销毁组件卸载时调用 dispose多个 Sheet 拖动时互相干扰使用了共享容器或共享状态每个实例使用独立容器和独立配置滚动时 CPU 占用异常Canvas 尺寸未随容器变化更新ResizeObserver 监听容器尺寸触发重绘保存时数据不完整读取的是显示值而非存储值确认读取使用 raw 数据接口而不是 UI 展示数据这些坑不一定每条都和你相关但建议在接入前先过一遍后面少走弯路。6. 从“能跑”到“好用”Univer 在业务里的几种落地形态6.1 嵌入式数据填报与低代码平台低代码平台里最需要的就是这种“复杂单元格能力”。表单字段可以拖一个 Univer Sheet 组件进去业务人员直接在网格里填数。相比逐个拖拽文本框这种体验更接近大家认知中的 Excel 填报学习成本很低。配合单元格校验和下拉选择能实现比普通表单丰富得多的输入模板。我见过一个实际项目把“客户报价单”整个做成了一张 Univer Sheet。每行一个商品列包含名称、单价、数量、优惠方案最后一行用公式自动算总价。这种设计比传统表单灵活很多业务人员调整行结构时不需要开发介入。6.2 数据中台看板与数字驾驶舱数据中台经常需要展示多维表格以前的做法是用 ECharts 做图表下面配一个普通表格。Univer 可以在这里承担更重的展示任务比如做透视表、分组汇总、多级表头。内部展示的表格不一定需要太强的编辑能力但要求支持冻结首行、列宽拖拽、数据高亮Univer 都能覆盖。6.3 私有化在线协作空间很多企业不希望业务数据放到外部在线文档平台上但内部又需要多人协作编辑同一个表格。用 Univer 自建一套私有化在线表格是很快能落地的路子。运营、财务、产品几个角色同时维护一份数据各自改动通过协同服务同步后台留存完整操作记录。这种形态下品牌定制、权限审计、数据导出都能自己做比直接买商业 SaaS 更可控。6.4 给 Univer 写一个自定义命令插件扩展 Univer 的方式很有“造工具”的味道。比如业务里经常要做“把选中区域所有空值改成 0”这个功能默认菜单里没有但实现起来很简单注册一个命令读取选中区域遍历单元格如果值为空就写入 0。自定义命令大致长这样示意结构具体以版本 API 为准function execute(context) { const { univerAPI } context; const workbook univerAPI.getActiveWorkbook(); const sheet workbook?.getActiveSheet(); const selection univerAPI.getActiveSelection(); const range selection?.getRange(); range?.forEach((cell) { if (cell.getValue() null || cell.getValue() ) { cell.setValue(0); } }); return true; }实际开发中你还需要把命令注册到命令管理器并把命令绑定到自定义菜单上。这个过程看起来绕但带来的是非常强的扩展边界几乎任何产品需求都能用“加一个命令”来表达。7. 我个人的几点选型建议Univer 给我的整体感觉是它值得技术前瞻性团队认真一试但不是那种“装个包就结束”的组件。它需要的不是写几行调用代码而是对表格模型、命令模式、渲染机制有一定的理解成本。团队里最好有前端功底比较扎实的同学来负责封装把复杂 API 收敛成业务团队友好的接口。如果让我给三个建议第一是不要在项目里散落调用 Univer 的 API统一封装在服务层对付它的版本迭代会从容很多。第二是先用官方示例加预设包跑通最小闭环再考虑自定义渲染、协同服务这些进阶能力不要一上来就追求大而全。第三是明确业务边界尤其是“文件兼容性”和“协同能力”这两件事越早和业务方达成一致后期越不会被动。我也必须诚实地说Univer 目前还不是一个完全成熟的“Office 套件”它的文档、生态工具链、周边案例都还在成长。但从在线表格这个细分领域来看它已经在用很正确的方式做事。如果你正在为 Web 应用找一个能长期投入的表格底座Univer 在技术选型清单里应该有它一个位置。