最近我在给一个 SaaS 项目做技术选型,业务方甩给我一个需求:用户要在浏览器里直接编辑表格,看起来得像 Excel,能用公式、能冻结行列、能导出 xlsx,后来还追加了多人同时改同一个工作簿的条件。我的第一反应是拒绝,这等于往产品里塞一个桌面级办公软件。但冷静下来后,我把目光锁定在 univer 这个开源项目上。
当时我在搜索引擎里敲下 "univer" 三个字,后面冒出来最多的是 "univer 在线" 这个词,很多人搜索的目的很明确:想知道它有没有在线体验、能不能跑在 Web 端、能不能拿来内嵌。这里先说结论:univer 是一个基于 TypeScript 开发的开源办公套件 SDK,它的定位不是又一个网页版 Excel,而是让开发者把电子表格、文档、幻灯片这类能力"装配"到自己的应用里。它解决的问题很具体:内嵌可编辑表格通常意味着从零写公式引擎、渲染引擎、选区交互,而这些在 univer 里已经以模块化的方式存在了。
这篇文章适合正在做表格、低代码、数据中台、教育或财务 SaaS 的前端开发者阅读。我会按照我真实踩坑的顺序来讲:为什么选它、怎么跑起来、里面的架构思路是什么、真实项目里会遇到哪些坎,以及哪些场景我劝你别用它。
1. 选型前的纠结:为什么我会盯上 Univer
1.1 自研表格引擎,"免费"其实是最贵的
很多团队听到"内嵌表格"的第一反应是自己画一个。前几年我也这么干过:用 HTML 表格拼一个可编辑区域,再加上 layer 悬浮面板处理合并单元格和公式输入。撑过万行、十几列没问题,但到了十万行,浏览器 DOM 节点先撑不住;到了公式嵌套,逻辑开始失控;到了多人协同,你甚至不敢想。
自研成本往往被低估。我算过一笔账:把"输入编辑、选区拖拽、复制粘贴、公式解析、条件格式、冻结行列、缩放滚动"这些 Excel 基础体验做及格,一个 5 人前端小组至少要投入大半年;再加协同、权限、导入导出,基本是没完没了。而且这些功能 bug 极多,比如剪贴板在 Mac 和 Windows 下行为完全不同,处理起来非常消耗精力。
1.2 Univer 想解决的根本问题
我当时看了 univer 官方文档和源码仓库之后,最大的感受是:它没有把"电子表格"当作一个单页功能,而是当作一套可插拔的框架。底层有独立的核心数据结构,上层有渲染引擎、UI 组件、公式引擎、协同模块,整个项目用 monorepo 方式组织,每个能力拆成独立包。
这样的设计带来的直接优势是:你可以只使用它的 Sheet 能力,不引入不需要的模块;也可以在它的插件体系上挂自己的业务逻辑,不必去改底层源码。它想解决的问题不是"帮你做一个 Excel",而是"让不同团队都能低成本搭建一个办公应用"。这个思路和我当时的处境非常匹配:我们不是要卖办公软件,只是需要一个稳定、可扩展的表格底座。
搜索时会出现 "univer 在线" 也不是偶然。Univer 官方提供了可以在线体验的演示环境,很多人的第一印象就是打开浏览器就能用。这一点对做 SaaS 内嵌特别重要,如果产品能直接在浏览器里完成编辑,用户就不需要安装插件,不需要本地文件命中的兼容问题。这也是我把它列入候选的几个关键原因之一。
2. 第一次跑起来:环境准备与最小 Demo
2.1 环境要求与项目初始化
我建议用一个干净的 Vite 项目来试水,不要一上来就丢进老业务里。univer 的客户端代码是纯 TypeScript,对框架没有强绑定,Vue、React、甚至原生 JS 都可以接入。我先用一个纯 TS 工程跑通了再考虑封装。
初始化很简单:
npm create vite@latest univer-demo -- --template vanilla-ts cd univer-demo npm install这里要说一个新手很容易忽略的点:univer 的版本迭代很快,而且 API 在不同 minor 版本之间都可能调整。我第一次照着博客上的旧代码写,结果初始化使用的相关类名都变了,报错半天。建议安装依赖时锁定主版本,并直接到官方文档去查最新示例,不要轻信老文章里的代码片段,包括我下面的示例也只当作结构参考。
2.2 注册插件:从 0 到 1 的装配过程
跑通文档上最简示例的过程,很像搭积木。你需要先实例化一个 Univer 壳,然后往里注册插件。下面的代码是当时版本的大概样子,新版可能略有差异:
import { Univer } from '@univerjs/core'; import { UniverSheetPlugin } from '@univerjs/sheets-plugin'; import { UniverSheetUIPlugin } from '@univerjs/sheets-ui-plugin'; const univer = new Univer({ locale: 'zhCN', plugins: [ UniverSheetPlugin, UniverSheetUIPlugin, ], }); const workbook = univer.createSheet({ name: '订单登记表', rowCount: 1000, colCount: 30, });这段代码做成了三件事:第一是初始化一个空白工作簿,里面有 1000 行 30 列;第二是给工作簿挂上了公式能力;第三是渲染出包含工具栏、单元格区域、状态栏的完整界面。整个 UI 是通过 Canvas 自绘来实现的,所以一般情况下你不会看到一堆表格 DOM 标签。
跑通之后,建议立刻做三件事:看看浏览器控制台的警告、确认工具栏出现并能输入公式、试试 Ctrl+Z 撤销。如果这三个都正常,说明基础链路没问题。
2.3 跑通后的第一个意外
我第一次跑起来的意外不是报错,而是它太安静了。没有任何红屏,直接出现一个非常完整的表格界面,浏览器请求列表里几乎看不到大量资源加载。原因在于它使用了 Canvas 渲染,大部分绘制工作都在 JavaScript 层完成,没有产生成千上万的表格节点。这个特点在后来的大数据量测试中帮了大忙。
另一个需要注意的点是 CSS 样式隔离。univer 的 UI 插件会往页面插入大量样式,如果你在既有项目里使用,最好把它放进独立的容器内,并且留意全局样式冲突。说白了,它默认样式是完整自带的,但你也别指望它和你的设计体系无缝贴合,灰底工具栏、蓝色主题遮罩这些部分后续都需要做定制。
3. 拆开引擎看架构:命令、插件和它的消息链路
3.1 从 Core 到 UI:五个可以落地的模块
我梳理 univer 源码时,把它理解成五个层次:核心数据层、计算引擎层、行为命令层、渲染展示层、业务应用层。
核心数据层里是工作簿、工作表、单元格、范围这些模型。计算引擎层负责公式解析与求值。行为命令层是 univer 比较有特色的部分,几乎每一次用户操作,比如改单元格、插入行、设置样式,都会被封装成一个 command 对象。渲染展示层采用 Canvas 绘制表格网格和文本,UI 层则提供工具栏、右键菜单、弹窗这些交互元素。
这个分层最直接的收益是:业务逻辑可以脱离界面单独测试。我可以在没有 DOM 的情况下,直接调用 API 去修改单元格数据、执行公式计算,这在写单元测试时非常舒服。很多前端表格库把数据和视图绑得太死,一套点击流程跑完才能看到结果,而 univer 的核心层更像一个独立的"表格大脑"。
3.2 命令机制:为什么它敢做撤销重做
我真正佩服的是命令机制。每一次操作都走同一个链路:监听用户动作、生成 command、执行 command、广播 command 执行事件。这样做至少有四个好处。
第一,撤销/重做不需要靠"操作前深拷贝全量数据"这种笨办法,而是把每个对立的操作串成命令队列,重新执行命令即可。第二,协同同步时可以只传播命令增量,不需要把整个工作表的状态发到每个客户端。第三,业务端可以嗅探每一次操作,记录审计日志。第四,你可以拦截命令,在命令执行之前做鉴权或数据校验。
这点对业务开发非常重要。比如要记录"谁在几点几分改了哪个单元格",我只需要监听命令事件,解析出改动范围值,就能很轻松地拼出审计日志。如果自己做表格引擎,这种级别的可观测性要花不少时间才能搭出来。
3.3 插件是一等公民
Univer 把几乎所有扩展能力都统一到插件这个概念里。你想新增一个函数、一个工具栏按钮、一个自定义渲染层,都可以通过插件挂载进去。这种方式不是敷衍的插件化,它的插件机制深入到命令注册、UI 渲染、资源释放等各个环节。
以自定义渲染为例,你可以通过渲染层 SDK 在特定单元格上方绘制额外的图形,这在做数据标注、批注提示、状态小红点时特别有用。插件还可以接收生命周期事件,比如在表格初始化完成后、销毁前做资源清理,减少内存泄漏的风险。
我用下来有个很深的体会:这种架构解决的是"团队协作的边界问题"。一个多人前端团队在同一个项目里开发,只要约定了插件接口,每个人负责一个独立模块,代码冲突就会大幅减少。我们后期把"导入 Excel 清洗数据""自定义公式计算""快捷键控制"分别交给了三个同事,并行开发时几乎没有互相干扰。
4. 真实项目里的心跳时刻:协同、公式和性能
4.1 协同编辑:在线两个字背后的 CRDT
很多人搜 "univer 在线",其实是冲着协同编辑去的。多人同时在一个工作簿上操作,这比普通的富文本协同难得多。难点在于:不同用户可能同时修改同一个单元格、插入同一行、或者某人删除了另一个人正在编辑的列。
Univer 的官方方案里用到了基于 Yjs 的 CRDT 框架来处理这种冲突。Yjs 采用了类 YATA 的 CRDT 算法,简单说就是给每个操作分配一个唯一的 ID 和依赖关系,并发修改最终能收敛到同一个结果,而且不需要一个中心服务器来排序。对我这种后端能力一般的纯前端来说,引入这类框架比自研 OT 算法要可靠得多。
但我要提醒的是:"项目用了 Yjs"和"你的产品做好协同编辑了"之间还有很长的路。你要自己处理移动端光标同步、未保存状态提示、连接断开后的重连补偿、以及别人改了你正在看的区域时的视觉提示。如果没有专门的协同研发投入,建议先做"共享编辑 + 乐观锁"的低配版,而不是一上来就上全量实时协同。
4.2 公式引擎的数据量警戒线
公式计算是我在测试中比较担心的一部分。Univer 内置了相当数量的常用函数,基础复合计算没有问题,但是我把一个 10 万行、20 列的表格塞进去,再在最后一列写一个逐行环比公式时,编辑卡顿感已经比较明显了。
原因是公式计算默认在主线程执行,单元格输入会触发依赖链上的重算。它不像 Excel 一样有高度优化的多线程计算内核,所以不要拿它跟桌面 Excel 拼大表格性能。我的建议是:若业务中确实存在大数据量,应该做分页加载、区域聚合计算,或者把高频计算放到后端,把结果回填到展示区域。公式引擎适合做交互式小数据计算,不适合做数仓级分析。
4.3 权限控制和单元格保护
办公表格接入业务系统,权限是躲不过去的。Univer 内部提供了工作表锁定的概念,可以设置单元格是否可编辑、行列是否可调整。但实际业务中的权限往往更细:销售只能改自己负责的客户列,财务才能改金额列,实习生只能看不能复制。
我采用的方案是事件拦截加业务侧校验。在命令执行通道上,我可以根据当前用户信息和操作范围做一个前置判断,命中权限规则就拒绝执行。这样做代码侵入小,也比只依赖 UI 层禁用菜单更见效,因为即便用户绕过界面通过控制台调 API,依然会走命令校验。数据入口再配一份更严格的后端校验,才算相对完整。
4.4 渲染细节与交互体验的坑
Canvas 自绘带来的性能优势明显,但细节坑也藏得多。比如单元格边框在缩放到 80% 时可能出现粗细不均;中文输入法在编辑框切换上偶尔出现延迟;Mac 上触控板横向滚动和 Windows 鼠标滚轮行为不一致。这些问题都不是阻塞性的,但你必须安排专门的兼容测试。
另外,复制粘贴的格式保留是很折磨人的环节。Univer 已经实现了从 Excel 复制内容再粘贴到表格里的能力,但复杂富文本格式、合并单元格区域的粘贴,仍然可能出现错位。如果你的用户大量使用 Excel 作为数据源,建议一定在内部测试里把"从 Excel 粘贴多区域合并表"作为一种回归用例长期维护。
5. 该不该选它:与同类方案摆在同一张桌上
5.1 表格内核选型对照表
没有只比 univer 而不看周围环境的道理,我选型时做了个简单对照:
| 方案 | 渲染方式 | 公式能力 | 协同 | 集成成本 | 适用形态 |
|---|---|---|---|---|---|
| Univer | Canvas 自绘 | 较好,模块化 | 有协作基础方案 | 中等 | 类 Excel 在线办公、内嵌表格 |
| Luckysheet | Canvas 自绘 | 有,成熟度一般 | 社区方案较少 | 中等 | 在线表格,维护节奏需要关注 |
| Handsontable | DOM | 基础公式较弱 | 需要自接 | 低 | 数据网格编辑,非高仿 Excel |
| AG Grid | DOM | 不主打公式 | 需自接 | 低到中 | 大数据量数据网格 |
| SpreadJS | Canvas/JS 引擎 | 很强 | 商业方案 | 中高 | 企业级类 Excel 系统 |
| OnlyOffice | 服务端套件 | 很强 | 自带 | 高 | 完整办公套件自建 |
从这个表可以看出,univer 在"开源 + 类 Excel 体验 + 可扩展架构"这个交叉区域里几乎没有直接对手。Luckysheet 当年人气很高,但维护节奏和模块化架构相对弱一些;Handsontable 与 AG Grid 更偏数据编辑网格,不是高仿 Excel 的体验;商业产品功能全,但授权费用需要单列预算。
5.2 我眼里它目前最合适的场景
我用下来的感受是,Univer 最适合的场景有两类。
一类是业务系统内嵌编辑:比如 CRM 里直接编辑客户数据透视表、财务报销单的明细清单、进销存里的商品价格表。用户不关心你是不是一个完整 Office,他们只想在一个页面里完成当前的表格任务,不要跳转第三方工具。
第二类是低代码平台、数据中台的表格组件底座:平台方需要把表单、列表、透视表组合到一个统一体验里,这种场景非常讲究模块复用和二次开发灵活性,Univer 的插件机制正好能对上。
反过来,如果你的核心场景是重度 Office 兼容,比如用户上传一份带复杂宏或数十种特殊图表类型的 xlsx,要求 100% 还原,那 Univer 现阶段不适合。它不是要替代 Excel,而是在 Web 应用里提供一个更现代的表格交互载体。明确这一点,后面很多需求都能谈得更清爽。
6. 二次开发,我建议从这三步开始
6.1 先读源码的哪几个包
如果你决定在 univer 上做二次开发,不要一头扎进所有源码。我建议先读三个包:核心的工作簿数据模型、命令相关的模块、插件注册机制。
明白数据模型是怎么组织行列、区域、合并单元格的,你就能避开很多坐标转换的坑。理解命令的注册和执行流程,你才能写出与官方交互一致的功能。搞清楚插件注册机制,你才知道哪些能力应该增强、哪些不该碰。
6.2 写一个自定义函数插件
二次开发从一个小而实用的自定义函数入手是最舒服的。比如业务里有个字段要按动态税率计算价税合计,可以注册一个自定义函数,让用户在单元格里像使用内置函数一样调用。大致逻辑是定义函数名、参数列表、求值函数,然后把求值函数透传给公式引擎。
我实际这么做一个插件用了半天不到,却让产品方案上了一个档次:用户不再是看到两个隐藏辅助列,而是直接输入=GST(1200, 0.13)就能得到结果。这类小而实用的插件,非常能体现 univer 架构的灵活。
6.3 自动保存:把命令事件接上后端
自动保存也是高频需求。我接后端的方式是监听命令执行事件,把影响单元格的改动收集起来放进队列,配合防抖,在停止操作几秒后用一个合并接口提交到后端。这样既避免了每次输入都请求一次后端,又能保留比较细致的变更记录。
后端保存时不能只存整个文件快照,否则版本对比功能会很吃力。建议存三段信息:完整数据快照、这次变更的补丁、操作者基本信息。这样做历史版本回滚时,可以快速找到任意时间点的状态。
6.4 最后留给后来者的几句话
如果你只是想让内部工具里有一块随手写写算算的表格区域,别急着上协同、权限、自绘主题这些高级能力,先把最基础的编辑、公式、导入导出跑稳,比什么都重要。我在实际项目里最花时间的从来不是 UI 特效,而是数据联动、边界条件和回归测试。
Univer 作为一个还很年轻的开源项目,API 变化会在一段时间内让开发者有些不舒服,但它的架构方向是对的。把公式、渲染、协同这些原本要自研的重资产拆成可插拔模块,让我这种做业务的前端也能搭出一个正经的在线表格应用。这份体验,放在两三年前是我不敢想的。