聊到给业务系统集成在线表格,前端的同学估计都有点一言难尽。商业组件功能全,但授权费用高、源码封闭,想改个交互都得跟客服扯皮;开源方案里 Luckysheet 算是不错的,可它把大量逻辑绑在 DOM 和旧架构上,玩到中后期总会觉得伸展不开。今天要聊的 Univer,就是 Luckysheet 作者团队做的下一代项目,一句话概括:一套基于 TypeScript + Canvas 渲染的模块化在线 Office 套件,目前表格能力最成熟。这篇文章会从它要解决什么问题讲起,再拆一拆底层的渲染、数据、公式、插件架构,最后给一份能跑起来的接入实践和踩坑记录。
如果你正在做 SaaS 产品、内部管理系统,或者单纯想在项目里嵌入一个可控的电子表格能力,Univer 值得花半小时摸清楚。它最大的价值不只是“又有一个在线 Excel 组件”,而是把表格、文档、幻灯片统一到一个架构里,同时用插件机制把渲染层、数据层和业务层彻底解耦——这意味着你可以只拿它的数据处理能力,也可以只用它的界面,甚至可以把它嵌到自己写的编辑器里。
1. Univer 是什么,它到底想解决什么问题
1.1 从 Luckysheet 到 Univer:一场“推倒重来”
很多早期用户知道 Univer,是因为熟悉 Luckysheet。Luckysheet 当年确实火过,它用一个还算轻量的思路在浏览器里实现了类 Excel 的在线表格,对中小型项目帮助很大。但它有一个根本问题:底层基于 DOM 构建单元格,数据量稍大、公式链一长,渲染和计算性能就直线下降,而且代码结构里 UI 逻辑和业务逻辑纠缠在一起,后续想加协同、加自定义扩展,越改越痛苦。
Univer 走的是另一条路。它在设计初期就决定,渲染引擎用 Canvas 重写,数据模型独立成一套文档模型,公式引擎做成可独立调用的纯 TypeScript 模块,UI 层则通过插件系统按需组合。这种“推倒重来”的选择,让 Univer 在架构上是面向未来十年的办公套件去做的,而不是在一个老代码基础上打补丁。也正因如此,它才能在极短时间里得到不少开源社区的关注。
1.2 它能做什么,适合谁用
当前 Univer 完成度最高的是在线表格模块,包括:
- 单元格编辑、样式、行列操作,接近 Excel 的交互习惯;
- 公式引擎,支持大量常用函数,且计算过程异步化;
- 多实例能力,同一个页面可以创建多个表格应用;
- 官方提供 React、Vue 等主流框架的接入封装;
- 插件体系,可以自定义菜单、按钮、右键菜单、单元格操作等。
它适合的人群,我列个清单:
- 前端工程师:需要在自己的业务系统里快速嵌入表格编辑能力;
- 团队技术负责人:评估商业组件和开源方案时,需要一个性能、授权、可维护性都说得过去的选项;
- 数据产品经理:想低成本验证一个带表格编辑能力的 MVP,而不是一上来就采购昂贵控件;
- 开源爱好者:对在线 Office 技术栈(渲染引擎、公式引擎、协同算法)感兴趣,想读代码入门。
一句话:如果你的需求只是“上传 Excel-导出 Excel”,用 SheetJS 之类就够了;但如果你需要“用户在线编辑 + 公式联动 + 深层次体验定制”,Univer 才真正进入射程范围。
2. 核心架构拆解:为什么 Univer 敢用 Canvas 重写一切
2.1 渲染引擎:放弃 DOM 的取舍
传统表格组件,比如老版 Luckysheet,或者一些自研小表格,很容易走上“一个单元格一个节点”的路。这个方案在百行百列里还能扛,一旦到几千行、十几列,页面里的 DOM 节点数量直接爆炸,滚动起来卡顿不说,内存占用也感人。我见过一个集成此类组件的后台,用户筛选完 5000 行数据,整个页面拖动时像在看慢动作回放。
Univer 的渲染思路可以类比成“黑板模式”,整个画布按需绘制。它把表格区域绘制在 Canvas 上,只有用户看到的可视区域才被真正渲染;滚动、缩放时,通过重绘而非新建节点来完成交互。这样做的直接收益是:单元格数量对渲染性能的影响极大降低。那个在旧组件里卡成 PPT 的 5000 行数据,在 Univer 的 Canvas 方案下基本可以流畅拖拽。
Canvas 方案的代价也显而易见:浏览器原生编辑光标、输入法交互、文本选择这些能力要自己重新实现,可访问性也比 DOM 方案更难做。但 Univer 选择了一并解决这些“难啃的骨头”,换来的是稳定性和扩展性的上限大幅提高。如果你以前做在线表格相关调研,会发现 SpreadJS 之类商业产品也普遍走自定义绘制路线,这已经是行业共识,并不是 Univer 独有。
2.2 数据模型与命令模式
表格界面的背后,Univer 维护了一套文档数据模型(Document Model)。它的核心不是把 Excel 的 XML 结构直接搬过来,而是抽象成统一的表格单元(Cell)、行(Row)、列(Column)、工作表(WorkSheet)等数据对象,并且所有数据操作都通过命令(Command)来触发。
这个设计非常像游戏或编辑器里的“命令模式”:任何一次“写 A1 单元格内容”的动作,不是直接改内存里的值,而是生成一条命令,经过执行、记录,进入操作栈。好处是显而易见的,第一步就为 undo/redo(撤销/重做)铺好了路;更进一步,当你要实现多人协同编辑时,每条命令都可以作为一个最小同步单元,发送给远端、应用到远端文档上。Univer 的数据层分离,让协同能力不是“后续能不能加”的问题,而是“什么时候开放”的问题。
2.3 公式引擎:依赖图与异步计算
公式是电子表格的灵魂,Univer 把一个纯 TypeScript 写的公式引擎拆成了独立模块。为什么值得单列一段?因为“能算公式”和“公式算得对、算得快、算错了能定位”是两码事。
Univer 的公式引擎在计算时会构建依赖图:比如 A1 是 1,B1 是=A1*2,C1 是=SUM(A1:B1),这时候 B1 和 C1 都依赖 A1。当你把 A1 改成 10,那 B1、C1 需要重新计算,但其他无关单元格不需要。它按依赖关系建立“计算链”,尽量做到只算受影响的公式,而不是整张表全部重算。
这一点很重要,尤其当表格里有大量 VLOOKUP、SUMIF 这些引用类公式时,全量重算的耗时会很明显。Univer 还把计算过程做成了异步任务,不阻塞界面操作,算是踩在用户体感的刀刃上了。我自己在测试中特意建了一个 2000 行、每行三四个公式的表格,修改左上角输入后,联动刷新基本无感,这在旧方案里不现实。
2.4 插件机制:UI 与业务解耦
Univer 给使用者的另一大诱惑是插件体系。普通表格组件给什么界面就是什么界面,想加一个自定义按钮、一个侧边栏面板,相当折腾。Univer 把菜单、工具栏、右键菜单、快捷键都设计成了可注册的扩展点。
举个例子,你在做数据中台,希望用户在表格里选中一个指标时,右侧浮出指标说明卡片,并且有一个“一键生成图表”按钮。在 Univer 里,你可以注册一个插件,只维护自己的 UI 组件,监听表格的选择变化事件,然后往侧边栏容器里插入内容。基础表格的渲染和编辑功能,全部由预设插件提供。这种“只改业务层、不影响核心层”的体验,是我认为 Univer 最值得升值的地方。
3. 从零跑通 Univer:安装、初始化、接入 React
3.1 环境准备:项目初始化
这一节给一份当时实测能用的接入流程。需要说明的是,Univer 还处在快速迭代期,版本之间的 API 会有调整,下面代码对应 1.x 早期阶段的做法,现在接手项目时以官方最新 Quick Start 为准,思路不变,包名和初始化参数可能会有细微变化。
先创建前端项目,我用 Vite 的 React-TS 模板做演示:
npm create vite@latest univer-demo -- --template react-ts cd univer-demo npm install然后安装 Univer 相关依赖。最省事的方式是直接安装官方预设包,它会帮你把表格、公式、UI 等核心插件装好;如果你后续需要定制,再按需换成底层模块。
npm install @univerjs/preset-sheets @univerjs/core3.2 初始化一个最小可用的表格实例
Univer 的思路是:先创建一个 Univer 实例,再往这个实例里注册各种插件。下面这段代码创建了一个带完整表格 UI 的应用,挂载到页面上 id 为app的容器里。
import { Univer } from '@univerjs/core'; import { defaultTheme } from '@univerjs/design'; import { SheetsUniver } from '@univerjs/preset-sheets'; import '@univerjs/preset-sheets/lib/styles/index.css'; const univer = new Univer(); // 注册表格预设插件 univer.registerPlugin(SheetsUniver); // 创建并初始化一个表格应用 univer.createUnit(UniverSheetsPlugin, { container: 'app', theme: defaultTheme, });这里需要注意几点:
- 容器
app必须已经在 DOM 里,并且最好给它一个明确的高度。表格应用不像普通组件可以依赖内容撑开高度,Canvas 渲染需要真实的容器尺寸来确定可视区域。 - 引入样式文件很重要,很多人直接照抄核心代码,忘了引 CSS,页面出来就是一堆怪异布局。
createUnit这个动作,语义上是在 Univer 实例里新建一个文档单元。类似地,你可以多次调用,实现同一个页面里多个表格独立共存。
这段代码跑起来,你就能看到一个有工具栏、公式栏、单元格区域和标签栏的在线表格界面,可以输入数据、改样式、用基础公式。这就是 Univer 的“最小可用闭环”。
3.3 写入数据与公式联动测试
光有一个空表格没什么说服力,我们来造点数据。直接在界面里手动输入当然可以,但更常见的需求是:页面初始化时,就要把后端数据渲染到表格里。
Univer 的数据写法跟 Excel 底层逻辑类似,直接操作单元格的值。下面示例是在第一个工作表里写入 A1=1、A2=2、B1 里写入求和公式:
const workbook = univer.getActiveWorkbook(); const worksheet = workbook.getActiveWorksheet(); worksheet.getRange(0, 0).setValue(1); // A1 worksheet.getRange(1, 0).setValue(2); // A2 worksheet.getRange(0, 1).setValue('=SUM(A1:A2)'); // B1这里下标是 0 起点,(0, 0)对应 A1,(0, 1)对应 B1。写入公式后,你可以看到 B1 单元格直接显示计算结果 3。公式字符串的语法与 Excel 保持一致,等号开头、区域引用用冒号。
我建议刚上手的人,先用这个方式把公式和数据都跑通一遍,再去研究导入导出。因为公式引擎能不能正常工作,直接决定了你的核心体验是否成立。
3.4 在 React 项目里正确销毁与重建
在 SPA 项目里,比较常见的坑是:页面跳转后,Univer 实例还挂在全局,再次进入页面又创建新实例,两个实例抢占同一个容器或者内存泄漏。所以接入 React 时,必须做好生命周期管理。
这里提供一个封装思路:
import { useEffect, useRef } from 'react'; import { Univer } from '@univerjs/core'; import { defaultTheme } from '@univerjs/design'; import { SheetsUniver } from '@univerjs/preset-sheets'; function SheetApp() { const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!containerRef.current) return; const univer = new Univer(); univer.registerPlugin(SheetsUniver); univer.createUnit(SheetsUniver, { container: containerRef.current, theme: defaultTheme, }); // 组件卸载时销毁实例,避免内存泄漏 return () => { univer.dispose(); }; }, []); return ( <div ref={containerRef} style={{ width: '100%', height: 'calc(100vh - 50px)' }} /> ); }univer.dispose()这一步是关键。我见过太多同事刚接 Univer 时只顾着创建、忘了清理,结果在 React StrictMode 下,“创建->销毁->再创建”之后第二遍表格直接白屏。原因就是旧的实例没有正确释放,Canvas 资源还占着。
如果你用的是 Vue,思路完全一致:在onMounted里初始化,在onBeforeUnmount里调用univer.dispose()。只要抓住这两条生命周期,框架差异基本不存在。
4. 踩坑实录:Univer 常见问题与排查方法
4.1 白屏或表格高度异常
这是接入初期遇到最多的问题。Univer 渲染需要容器具备真实的宽度和高度。如果你把容器挂在了一个display: none的弹窗里,或者父级高度是 auto,初始化时容器高度为 0,表格自然什么都画不出来。
排查建议三步走:
- 先在初始化之前,用
console.log(containerRef.current.clientHeight)打印容器高度,确认不是 0; - 如果容器在弹窗/折叠面板里,等待弹窗打开后再初始化,或先用固定尺寸容器验证;
- 检查全局 CSS 是否重置了 canvas 相关样式,比如
img { display: block }之类的规则可能影响内联元素。
4.2 公式不生效,显示成了普通文本
碰到公式变成纯文本,原因大概率出在写入方式上。Univer 对公式和普通文本是有明确区分的,写入字符串时,以等号开头的字符串才会被识别为公式。如果你把'=SUM(A1:A2)'经过 JSON 解析或转义处理,等号前面多了不可见字符,公式就会失效。
另一个常见原因是公式区域引用用错了写法。中英文冒号、括号混用,也会导致公式解析失败。遇到这类问题,建议先写一个最简单的=1+1再逐步加引用,不要一上来就堆复杂函数。这样能快速定位是引擎问题还是写法问题。
4.3 自定义样式时,界面被“打回原形”
Univer 使用一套基于 CSS 变量的设计系统。很多人接入后喜欢在项目里加一条全局样式,比如* { box-sizing: border-box }或者改掉所有 canvas 的outline,但这类规则会波及 Univer 的 UI。
我这里给一个建议:不要直接用通配符去覆盖,给 Univer 的容器单独加一个作用域类名,再在类名下调整内部元素的样式。Univer 有自己的主题变量,比如--primary-color、--background-color等,修改这些变量比粗暴覆写具体元素更安全。真想换品牌色,优先去设计系统层面做,而不是写!important。
4.4 大数据量场景下的性能优化
Univer 的 Canvas 渲染已经解决了一部分性能问题,但不代表你可以往里面塞 10 万行原始数据。我自己实测下来,初始化时如果一次性写入超大数据集,主要瓶颈反而在数据构建和命令记录。有一个现象值得注意:创建矩形的写入操作会拆成多次命令,如果循环逐格写入,几千次 setValue 的操作栈会把性能打没。
更合理的做法是按区域批量写入。Univer 提供了getRange().setValues()这类批量方法,传入二维数组即可。你可以把数据组好一次性发出去,减少命令数量。另外,初始化过程可以关掉部分用于展示的功能(比如公式联动计算),等数据挂载完成后再整体重算。
4.5 快捷键与中文输入法冲突
最后分享一个比较隐蔽的坑。在中文输入法下,用户敲击数字或字母时,会经过一个“组词”阶段。Univer 在编辑态里处理键盘事件,如果组件只顾着听原生 keydown,很容易把输入法中间态的按键当成真正的编辑操作,导致用户打着打着字就被吞了。
目前 Univer 针对编辑状态做了组合键处理,但仍然建议你在大规模投入前,安排一轮真机中文输入法测试(包括搜狗、微软拼音、Mac 自带输入法)。如果发现个别场景和预期不符,优先去 GitHub Issues 里搜输入法相关 issue,而不是自己魔改编辑内核。
5. 选型参考:Univer 适合什么场景,不适合什么场景
5.1 和 Luckysheet、SpreadJS、OnlyOffice 的横向对比
我把几个常用方案放在一起,方便大家做选型:
| 方案 | 开源/授权 | 渲染方式 | 定制成本 | 协同能力 | 适用场景 |
|---|---|---|---|---|---|
| Univer | Apache 2.0 全开源 | Canvas | 较低,插件机制完善 | 数据层预留,路线图内有规划 | 深度业务集成、SaaS 产品 |
| Luckysheet | 开源(社区迭代趋缓) | DOM | 一般,二次开发空间受限 | 方案不成熟 | 旧项目维护、轻量使用 |
| SpreadJS | 商业授权 | Canvas | 中等,有成熟 API | 有配套方案 | 预算充足、要求稳定 |
| OnlyOffice | AGPL/商业双授权 | Canvas | 较高,整体是一套完整Office | 天然支持 | 需要完整 Office 协作场景 |
这里多说一句授权问题。OnlyOffice 的社区版是 AGPL 协议,如果你的产品是内部使用,AGPL 的约束相对可控;但如果你的产品要对外部开放、提供 SaaS 服务,AGPL 会带来很强的传染性风险,需要非常谨慎。相比之下 Univer 的 Apache 2.0 对商业集成友好得多,主流的闭源商业项目集成它几乎没有法律包袱。
5.2 什么情况下我说“别急着选 Univer”
Univer 虽然好,但也不是万金油。如果你遇到下面这几种情况,我反而建议你再想想:
- 你需要的是“完整 Office 兼容”。Univer 的表格能力确实在迅速迭代,但目前它对复杂公式、图表、透视表的覆盖,还没到和商业产品正面硬碰的阶段。如果你的客户明确要求“打开一个复杂 Excel 里所有元素都不能乱”,还是得认真做一轮兼容性测试。
- 你的产品需要一个“桌面级”的前端体验。有些场景要求表格像本地 Office 一样运行,快捷键、右键菜单、打印、批注、审阅这些都要求高完成度,Univer 目前还是 Web 为先的思路,桌面端体验不完全对齐。
- 团队缺乏 TypeScript 基础。Univer 的二次开发基本都得碰 TS,如果你团队一直在用 Java 写页面、对前端工程化不熟,上手成本会被放大。插件体系越灵活,对开发者素质的要求就越高。
5.3 我的建议路径
如果你没有历史包袱,新项目里确定要上在线表格,我的建议路径是:先在官方在线 Demo 把核心操作过一遍,确认交互能接受;再按第 3 节的步骤跑一个最小集成原型;然后把你自己最复杂的几个 Excel 文件导入进去,重点看公式、格式、合并单元格、数据验证这几个环节;最后再做一次输入法、性能、异常操作的实测。
整个过程大概两三天就能得出结论,比拍脑袋买一套商业组件再翻车要稳妥得多。我在技术选型上吃过不少亏,最大的体会是:开源项目好不好,不是看它的 Star 数和官方宣传,而是挖两层之后看它给你留了多少扩展入口。Univer 至少在这个维度给了我足够的信心。
最后再分享一个小技巧
实践里最让我舒服的一个点是:Univer 的初始化完全可以不用界面,只注册数据相关的插件,把它当成一个纯数据引擎用。也就是说,你可以在自己的 UI 里画一个简易表格,把数据同步给 Univer,让它帮你算公式、做依赖管理和快照恢复。这种“无头表格”的玩法,在对付复杂业务规则时非常有用。
我在一个配置化平台里就是只用 Univer 管理“单元格之间的公式依赖”,界面完全是自己的。算下来,公式引擎的判断和联动刷新花费的调试时间,比我自己写一套规则解析器省了不止一个量级。如果你接手 Univer 项目,建议也可以照这个思路玩一次,不为别的,就为了体会一下“数据层和渲染层解耦”到底能带来什么样的自由。踩过几次坑之后,我对这类项目的判断标准变得非常统一:能不能让我只取我所需要的部分,不被全家桶绑架。Univer 在这方面,是真的做到了。