☰
Univer实战解析:从Canvas渲染到公式协同的在线表格平台
2026/11/3 5:27:25 网站建设 项目流程

做前端的人应该都逃不过一个灵魂拷问:产品经理什么时候会提出“在网页里做个 Excel”这种需求。最初我以为给个 table 加个单元格编辑就差不多了,直到需求列表越来越长——要公式、要条件格式、要数据透视、要多人同时编辑,我才意识到真正的对手是 Univer。与其说它是一个开源表格控件,不如说它是一个能在浏览器里跑起来的 Office 基础平台,支持表格、文档、幻灯片三个套件,底层是自研的 Canvas 渲染引擎和命令式数据模型,用的是 TypeScript 技术栈。这篇文章我会从架构原理到实际集成,讲清楚为什么需要它、怎么用它、以及哪些地方真的会踩坑。

1. Univer 到底是什么:它不是又一个表格组件

1.1 从“网页展示表格”到“在线协同办公”

很多团队第一次接触 Univer,是因为想找一个能在网页里编辑表格的开源方案。市面上并不缺这类东西,Handsontable、ag-Grid、xlsx.js 配合自研编辑器,都能做到“长得像 Excel”。但真正的分水岭出现在三个需求出现之后。

第一个是公式。不只是 SUM、AVERAGE 这种简单函数,而是 VLOOKUP、IF 嵌套、数组公式、跨 Sheet 引用。第二个是协同。两个人同时打开同一个表格,A 改了 B2 单元格,B 的屏幕上要实时看到变化,还不能互相覆盖。第三个是复杂样式。合并单元格、条件格式、数据验证、冻结窗格、筛选——这些是 Excel 用户的肌肉记忆,缺哪一个都会被吐槽。

如果用传统 DOM 表格组件硬扛这三个需求,工期会非常痛苦。DOM 元素一多,性能立刻崩;公式引擎要自己写,词法分析、依赖树、错误传播,每一块都是硬骨头;协同更是要从 socket 通信往上全链路造轮子。Univer 之所以被越来越多团队采用,核心原因就是它把这三件事一次性做完了,而且做得比较深。

1.2 Univer 与 Luckysheet 的血缘关系

了解 Univer 的人一定听过 Luckysheet。这两个项目背后是同一个核心团队,Univer 本质上是对 Luckysheet 的架构级重构。Luckysheet 解决的问题是“在浏览器里渲染和编辑表格”,Univer 解决的问题更上一层:用一套统一的底层平台承载表格、文档、幻灯片,并且把渲染、数据、协同全部模块化。

这意味着什么?对开发者来说,如果你曾经为 Luckysheet 的性能天花板和混乱的插件机制头疼,Univer 是对症下药的。它把表格的每个能力模块化到 npm 包级别,你需要哪个装哪个,而不是引入一个巨大的框架然后被绑定在里面。

1.3 能做什么、解决什么问题

我搭过一套内部的数据填报系统,需求是让业务人员像用 Excel 一样录入数据,同时管理员能看到实时汇总的报表。之前方案是前端表格 + 后端 node-xlsx 解析上传,体验非常割裂,改一个数字要等页面刷新。换成 Univer 之后,录入、公式计算、条件格式标记异常数据、导出 xlsx,全部在一个页面里完成。

简单总结 Univer 的实际能力清单:

  • 在线表格:单元格编辑、行列操作、合并、样式、冻结窗格、筛选、排序
  • 公式引擎:300+ 函数,兼容 Excel 表达式,支持跨 Sheet 引用和数组公式
  • 协同编辑:基于命令同步的多人在线编辑,可自建服务端
  • 导入导出:xlsx、csv 格式的解析与输出
  • 内容套件:除了表格,还覆盖文档和幻灯片
  • 项目管理:官方提供 npm 包按需集成,无框架锁定

适合哪类人来用?如果你是准备自研在线办公产品、给企业内部 SaaS 增加表格模块、或者想做一个像腾讯文档那样的工具,Univer 值得重点评估。如果你只是想在页面上展示一个只读表格,那它有点牛刀杀鸡,直接用 HTML table 或者 JSON 渲染工具就行。

2. 核心架构拆解:为什么 Univer 敢说自己是个平台

2.1 自研渲染引擎:Canvas 才是表格的真归宿

表格组件性能的瓶颈往往是 DOM。一个 1000 行 x 50 列的表格就有五万个单元格,如果每个单元格是一个 div,浏览器光是布局就要卡成幻灯片。Univer 没有选择 DOM 树方案,而是用了自己写的渲染引擎,基于 Canvas 2D 绘制。

这个渲染引擎做了几件事。第一是分层渲染,背景层、单元格内容层、边框层、选区层、浮动对象层互相分离,某一层变化时只重绘那层,不必全表刷新。第二是视口裁剪,屏幕可见区域外的单元格根本不画,所以表格数据量再大,渲染负载只跟当前视口有关。第三是坐标换算与滚动优化,滚动过程走的是 Canvas 局部重绘加缓冲区,速度上远超普通 DOM 方案。

实际测试中,我往 Univer 里塞了五万行数据,滚动和编辑响应依然流畅。对用户来说,手指滑动表格就像在用本地 Excel,不是网页的“卡顿感”。

2.2 命令系统与数据层:一切操作皆可追溯

Univer 的数据层设计是它区别于普通表格组件的核心。用户对表格做的每一个操作——修改单元格、插入行、删除列、设置样式——都被封装成 Command 对象。

命令之上有统一的分发、撤销、重做机制。撤销不是一个一个历史栈存旧值,而是把“逆操作”定义清楚,任何一个命令都能找到对应反向命令。这套机制做了协同的基础:每个人的操作都是一条命令,协同服务端把这些命令按序同步下去,就能保证各端数据一致。

数据模型本身也走的是“数据与视图分离”。渲染层只负责把模型画出来,模型层不关心你用的是 Canvas 还是 SVG。这也是为什么 Univer 将来可以很容易换渲染后端,或者做服务端 headless 渲染。

2.3 公式引擎与协同引擎:两大护法

公式引擎是 Univer 最重的模块之一。它不是简单地调用 eval 解析表达式,而是有一套完整的求值流程。公式首先被编译成抽象语法树,接着按单元格依赖关系建立计算链,遇到嵌套引用会做拓扑排序,保证先算依赖项再算结果。

更麻烦的是交叉引用。比如 Sheet1 的 A1 单元格引用 Sheet2 的 B2,而 Sheet2 的 C3 又回头引用 Sheet1 的 A1。Univer 的公式引擎会建立跨 Sheet 依赖图,循环引用会被检测出来并提示错误,而不是死循环。

协同引擎则建立在命令系统之上。每个客户端产生的命令会带有一个序列号,通过 WebSocket 发送到协同服务端,服务端排序后再广播给其他客户端。这套设计和传统 OT 算法不完全相同,更接近“命令日志 + 状态同步”,实现起来干净很多。生产环境下如果你不想自己维护协同后端,Univer 官方也提供了协作服务端方案,可以部署在自有服务器上。

2.4 插件化架构:功能像积木一样组装

Univer 不是一个大而全的 monolith。官方团队把功能拆成了很多 npm 包,比如@univerjs/sheets、@univerjs/ui、@univerjs/engine-formula、@univerjs/sheets-ui等。装哪些东西,你自己的项目来决定。

这种设计的实际价值是纸面架构之外的东西。我记得最初集成时只装了 core、sheets 和 ui 三个包,界面干净,没有多余按钮。后来需要条件格式,再按需装对应插件即可。不会出现装一个表格控件结果带出一堆用不上的 CSS 和图标资源。

插件系统不仅对官方包开放,第三方也可以注册自定义命令、自定义 UI 组件、自定义渲染对象。这意味着你可以给 Univer 加一个“数据导入向导”插件、加一个“图表”插件,业务定制都在标准通道里进行,而不是魔改核心代码。

2.5 移动端与跨端支持

现在很多管理后台是移动端优先,用户拿手机审批、查数、录数据是常态。Univer 对触控做了支持,单元格点击、拖选、滚动可以通过触摸完成,还支持捏合缩放。在响应式布局上,表格传入容器后会自动适配大小,不挑屏幕。

我实际在 iPad 上试过用它打开的报表,操作体验跟桌面差别不大,这对企业内部的移动审批场景价值很高。不过手机屏幕毕竟小,公式编辑和复杂样式调整还是建议放在桌面端做。

3. 实操:从零到一集成一个在线表格

3.1 环境准备:确定版本与依赖范围

先说明,Univer 迭代速度很快,API 在不同版本之间会有调整。我下面用的版本以 0.3.x 系列为基准,正式写代码前建议去官方 GitHub 仓库确认最新版本。

创建项目基础结构,我用的是 Vite + TypeScript,Univer 本身框架无关,React/Vue 甚至原生 JS 都能接。项目里你只需要确保 Node.js 版本在 18 以上,然后安装核心依赖:

npm install @univerjs/core @univerjs/sheets @univerjs/ui @univerjs/sheets-ui @univerjs/engine-formula @univerjs/engine-render @univerjs/locales

提示:@univerjs/ui默认会加载完整工具栏和菜单,如果你只想要一个纯表格区域,UI 插件有对应的配置项控制哪些按钮显示。

3.2 初始化 Univer 实例

初始化 Univer 分为两步:创建实例、注册插件。插件注册顺序会影响渲染和功能加载,建议按照“引擎 -> 数据套件 -> UI”的顺序来注册。

import { LocaleType, Univer } from '@univerjs/core'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverUIPlugin } from '@univerjs/ui'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; const univer = new Univer({ locale: LocaleType.ZH_CN, themes: [], }); univer.registerPlugin(UniverRenderEnginePlugin); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container: 'app', layout: { toolbar: [], }, }); univer.registerPlugin(UniverSheetsUIPlugin);

我在第一次集成时漏掉了UniverRenderEnginePlugin,结果页面一片空白,连报错都没有。这个教训放在后面问题排查里细说。核心逻辑是:UI 插件负责界面壳子,渲染引擎负责画画,两者缺一不可。

3.3 创建带数据的工作簿

实例和插件都准备好了,下一步就是在页面上创建一个具体的工作簿对象。

const workbook = univer.createUnit( UniverInstanceType.UNIVER_SHEET, { id: 'workbook-01', name: '销售数据看板', sheetOrder: ['sheet-01', 'sheet-02'], sheets: { 'sheet-01': { id: 'sheet-01', name: '月度明细', cellData: { 0: { 0: { v: '产品' }, 1: { v: '销量' }, 2: { v: '单价' }, 3: { v: '总价' }, }, 1: { 0: { v: '白酒'}, 1: { v: 120 }, 2: { v: 380 }, 3: { v: { f: 'B2*C2', v: 45600 } }, }, }, rowCount: 200, colCount: 30, }, 'sheet-02': { id: 'sheet-02', name: '汇总页', cellData: { 0: { 0: { v: '总销量' }, 1: { v: { f: 'SUM(Sheet1!B2:B200)', v: 0 } } }, }, }, }, } );

注意cellData里我用了{ f: 'B2*C2', v: 45600 }这种数据结构。f是公式字符串,v是前一次计算出来的缓存值。设置缓存值不是必须的,但建议带上,这样页面首次渲染时公式引擎还没开始异步计算,也能先展示旧结果,肉眼感知更快。

如果想要生成一组随机模拟数据,用循环往cellData里塞即可。因为cellData是一个以行号为 key 的对象,不是数组,写入稀疏数据非常方便,这跟 Excel 的稀疏矩阵模型一致。

3.4 接入协同编辑:从单机到多人在线

单机版表格再好,也只是“高级表格控件”,真正让它变成在线协作产品的是协同。Univer 的协同接入比想象中直接:客户端不需要自己处理 OT 合并,统一由命令系统发出操作,通过协同服务端做同步。

先安装协作插件:

npm install @univerjs/sheets-collaboration

然后在初始化时注册插件并配置连接信息:

import { UniverSheetsCollaborationPlugin } from '@univerjs/sheets-collaboration'; univer.registerPlugin(UniverSheetsCollaborationPlugin, { documentId: 'wb-demo-001', server: { host: 'wss://your-collab-server.example', token: 'your-token', }, });

协同服务端需要自己部署,官方提供了单独的协作服务端项目,技术栈是 Node.js + WebSocket,文档里有完整的 Docker 启动方式。启动起来之后,各端只要配置同一个documentId,打开同一个工作簿,就能实时看到对方的修改。

如果你是第一次搭协同服务端,建议先用本地 WebSocket 做冒烟测试,确认网络连通、鉴权通过,再往生产环境切。协同最怕的是网络不稳定时操作丢失,测试阶段一定要模拟断连、弱网、多端同时编辑同一单元格这些场景。

3.5 自定义业务功能:注册自己的命令

Univer 允许你注册业务命令,这也是把它嵌进现有后台管理体系的关键能力。比如我有一个需求:点工具栏按钮,给选中的单元格打上“已审核”标记,并写入操作日志。

实现方式大致是:继承 Command 基类,定义命令 ID,实现对应的执行逻辑和撤销逻辑,然后用univer.registerCommand注册。这样 UI 层只需要往工具栏加一个按钮,点击时触发命令即可。

把业务动作做成命令的好处是自动获得撤销、重做、协同同步能力。其他用户能看到你的“审核操作”实时出现,而且如果操作有误,管理员还能一键撤销,这对审核类场景特别有用。

4. 常见问题与排查技巧实录

4.1 页面白屏:最离谱也最容易踩

第一次接入 Univer 时最常见的故障就是白屏。原因有几种,按出现频率排序:

  • 注册插件时漏掉了渲染引擎插件
  • 忘记传入或标错容器 DOM 的 id
  • 动态加载时容器尚未挂载,初始化代码跑太早

排查思路:先把初始化脚本放到页面 onload 之后,确认容器存在;然后打开控制台看有没有报错,重点看是否有像createRenderer未定义之类的提示;最后确认使用的是不是最新版本的包,Univer 包内部之间有版本依赖,不同 minor 版本混装容易炸。

分布式问题可以这么排查:两三个包版本不匹配时,大多数错误会表现为“某个方法不存在”或“某个模块加载失败”。建议统一使用同一个版本号,不要一个包用 0.3.2,另一个用 0.2.9。

4.2 大数据量卡顿:优化渲染与公式

Univer 对五万行数据已经能做到流式加载,但如果你把十万行、上百列一次性造出来,即便 Canvas 也扛不住。这时候可以做两件事:

  • 限制初始渲染行列数。rowCount和colCount可以设小一点,用户滚动到边缘时再按需扩展。
  • 公式计算分批进行。可以通过配置项让公式引擎在空闲时间计算,也可以把重计算改为手动触发。

一个来自实际的经验:业务方非要一次性导入二十万行,我建议他们改用“服务端聚合 + 前端表格展示汇总结果”的方案。表格不是数据库,真正要人眼逐行看的数据不会超过几万行,超出这个量级的产品设计本身就值得反思。

4.3 协同冲突与数据不一致

协同场景下最怕出现“我明明改了 A2,结果刷新之后又变回去了”。这种情况通常有几种原因:

  • 协同服务端切了文档 ID,两个客户端连到了不同的协同节点
  • 操作命令的本地执行与服务端确认顺序不一致,导致回滚
  • 断线重连后未拉取最新快照,本地状态覆盖了远程状态

排查时先看 WebSocket 连接是否稳定,再对比所有客户端的documentId、工作簿 ID 是否一致。Univer 的协同命令有序列号,正常情况下不会出现并发覆盖,如果出现了,大概率是接了多个协同服务端实例,但共享存储没有做好。

4.4 常见问题速查表

问题现象可能原因解决办法
页面白屏、无任何元素缺少渲染引擎插件或容器 id 错误检查插件注册顺序与容器选择器
表格能显示但无法编辑未注册 sheets-ui 插件添加 UniverSheetsUIPlugin 注册
公式显示 #NAME?公式引擎未注册或函数名不匹配确认引擎插件已装入,函数名遵循 Excel 语法
协同不实时同步WebSocket 未建立或 documentId 不一致检查服务端连接、统一文档 ID
导入 xlsx 后样式丢失当前版本对复杂样式支持有限换用官方 premium 插件或做降级展示
移动端误触频繁未配置触控优化检查 UI 插件触控相关配置

4.5 自己踩过的几个坑

第一个坑是混装包版本。Univer 核心包和 UI 包有着严格的对应关系,我有一阵子为了使用某个新功能单独升级了@univerjs/sheets-ui,结果核心包还是老版本,页面直接白屏。后来学乖了,每次升级都整体更新,然后用官方示例仓库的 package.json 做对照。

第二个坑是数据更新不触发 UI 刷新。直接用workbook.getActiveSheet().getCellData()拿到对象后修改对象的属性,以为会触发视图更新,实际上 Univer 的数据是响应式的,要通过命令系统或官方 API 更新。正确做法是用命令或 set 方法改变数据,视图会自动重绘。

第三个坑是销毁实例。单页应用里切换到其他路由后,Univer 实例和它的容器不会自动清理。如果你在同一个容器里重新初始化,会出现事件监听重复、DOM 残留的问题。记得在组件卸载时调用univer.dispose(),反复切换路由的情况尤其要注意。

5. 应用场景拆解:到底哪些项目该用 Univer

5.1 企业内部数据填报与审批

很多企业都有这种需求:给业务部门一个表格模板,让大家填写月度数据,然后管理层在线查看汇总结果。以前的做法是发 Excel 文件到群里,收回来后手动合并,既慢又容易错。

基于 Univer 可以做一个标准方案:模板通过 xlsx 导入,字段校验用数据验证插件,填写后公式自动汇总到总表,再加一个审核命令流,管理者点击按钮完成审核。整个过程在浏览器里完成,不需要装 Office,这种场景的体验代差是巨大的。

5.2 数据分析平台与报表看板

电商、零售、金融行业的管理后台离不开数据透视和筛选。用 Univer 手动实现数据透视不太现实,好在官方提供了数据透视插件。报表模块可以把服务端返回的 JSON 放到表格里,同时加几条常用排序、筛选规则,用户就能自助分析,不再需要每次让开发写一张临时页面。

我在一个项目的销售看板里,用 Sheets + 条件格式把异常低价的订单标成红色,运营打开页面扫一眼就知道哪些单子要人工复核。

5.3 SaaS 产品嵌入:给你的用户一个表格

如果你做的是一个 SaaS 产品,比如项目管理工具、招聘管理系统、财务记账软件,用户可以导入 Excel、在线编辑、再导出,几乎是一上手就必须有的能力。Univer 在这里的角色不是“展示组件”,而是产品的一部分。

因为插件化的设计,SaaS 团队可以把品牌 logo、自定义菜单、业务校验全部嵌进去,用户用起来不会觉得这是一个半生不熟的第三方控件。甚至可以把多人协同变成会员功能,深度整合到订阅体系里。

5.4 教育场景与在线协作办公

教育领域里,老师发一个空白表格,学生在各自终端填写,老师实时看到进度,这个互动体验非常适合 Univer。幻灯片套件也可以用于课件展示。虽然目前文档和幻灯片模块的成熟度不如表格,但基础框架已经铺好,后续演进空间很大。

如果只是想把表格能力植入到已有富文本编辑器里,Univer 也可以作为子模块嵌入,在编辑器中内嵌表格弹窗,流程上不冲突。

6. 移动端与性能调优实践

6.1 移动端适配的关键配置

手机访问表格时,最大的痛点是误触和看不清。Univer 的 UI 插件支持配置工具栏是否在移动端显示,建议移动端只保留核心按钮:撤销、重做、字体大小,其余全部收进“更多”菜单里。单元格双击和长按行为也要测试,避免把“长按选中区域”和“长按弹出菜单”混在一起。

项目里我把工具栏按屏幕宽度做了动态裁剪,小屏只显示五个按钮,同时调大了点击热区。这个细节用户不会说,但实际满意率提升不少。

6.2 渲染性能压测思路

上生产之前建议做一次压力测试:五万行数据 + 一百个公式 + 模拟协同三端同时操作。浏览器性能面板里主要看两个指标:滚动时的帧率是否稳定在 40fps 以上、公式重算后的首帧渲染时间是否过长。

如果发现滚动卡顿,优先检查是否误开启了整表组件刷新。Univer 的插件体系里有些 UI 组件会主动订阅数据变化事件,一旦数据频繁变动就会触发大量冗余重绘。精简插件、限制事件订阅范围,是性能优化的第一刀。

6.3 打包体积与控制

Univer 的包体积不小,全套插件加载可能会有几兆的 JS 资源。这对后台工具可接受,但如果是面向普通用户的 C 端产品,就要花心思做拆包了。

建议方案:首屏只加载核心表格渲染,公式引擎懒加载,协同插件在用户点击“协作”按钮后再动态 import。实在不行,至少把公式引擎按需启用,因为它是最重的模块之一。按需加载做完之后,我们项目的首屏 JS 体积降了差不多四成。

7. 生态现状与选型决策建议

7.1 开源协议与商业化路径

Univer 项目采用 Apache-2.0 协议,对商业使用友好,可以自由修改、分发。有一部分高级功能(如图表、高级打印、更多数据源连接)属于商业版本范畴,但基础表格能力完全够用。

选型时务必把“开源版够不够我用”这个问题提前想明白。如果确定要图表、要 PDF 导出、要更完善的协同服务端运维支持,预算可以投向官方商业授权,这时的角色更像采购成熟产品,而不是被技术锁定。

7.2 和同类开源方案怎么比

对比项UniverLuckysheetHandsontable自研 xlsx.js 方案
公式引擎完整内置有但性能一般不支持需自研
协同编辑官方方案社区方案不成熟不支持需自研
多端适配桌面+移动端桌面为主桌面为主看自己
扩展机制插件化弱弱无
开源协议Apache-2.0GPL(有争议)商业收费自定义
社区活跃度高,持续更新已停止活跃维护商业维护看团队

如果你是中小团队,不想背上 GPL 义务,又用不起 Handsontable 的授权,Univer 几乎是兼顾能力与合规的最优解。如果项目只是展示只读表格,用不着上 Univer,一个轻量 JSON 转表格方案更划算。

7.3 什么情况下不建议用 Univer

Univer 不是银弹。如果你的业务对 Excel 文件格式的兼容性要求极高,比如需要完整还原 docx/xlsx 里所有不常见图表、宏、VBA,那 Univer open-source 版本现阶段无法满足,这种情况适合评估商业套件如 OnlyOffice。

如果你的团队完全没有前端工程化和 TypeScript 经验,Univer 的学习曲线一开始可能会让你想放弃。它不是一个开箱即用的 SaaS,而是一套需要按业务要求组装的基础设施,前期投入比 Vue 里放个表格组件要大,但配套收益也大得多。

我从实际项目里的体会是:Univer 的选型逻辑不是“哪个表格控件好用”,而是“我到底是想做个带表格的页面,还是想拥有一个能长期迭代的在线办公产品”。想清楚了再上车,后面代码写起来会顺很多。

8. 写在最后的一点经验

折腾了这些天,我最大的感受是 Univer 已经把在线办公里最难的几块——渲染、公式、协同——都做成了基础设施,剩下的空间留给你做业务。团队不再需要从零研究 Canvas 表格怎么写、OT 怎么合,而是可以把精力放在报表模板设计、权限体系、与公司现有账号系统的对接上。

最后分享一个小技巧:把 Univer 的实例用依赖注入包在业务层外面,所有业务代码只依赖你自己的接口,比如sheetService.setCellValue(row, col, value),不直接触摸 Univer 核心。这样万一未来需要升级大版本,或者把表格层换掉,业务代码几乎不用改。我因为一开始没有做这层封装,后来升级版本时改调用 API 改到想骂人。你要是刚起步,务必先把这层隔离做出来,省下的时间足够看几十集剧。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询