☰
Univer开源表格引擎:中后台在线Excel与协同编辑实战
2026/11/5 4:56:26 网站建设 项目流程

如果这几年你做过任何跟“中后台表格”沾边的产品,应该对那个时刻有印象:需求文档里写着“在线编辑、多人协同、支持Excel导入导出”,产品经理还不忘补一句“最好不用装客户端”。开发现场沉默两秒之后,通常的结局就是先自研一个能看不能编的表格,然后把“导出Excel”按钮做得尽量显眼。Univer这个名字,就是在这种尴尬里被越来越多团队注意到的。

Univer是一个开源的企业级文档解决方案,目标不是再造一个孤立的grid组件,而是让网页应用直接拥有接近Office 365的表格、文档和幻灯片体验。核心代码基于TypeScript,目前分成Sheet、Doc、Slide三大模块,其中Univer Sheet是最成熟、日常被调用最多的部分,也是这篇文章主要想聊透的东西。它解决的不是“某个单元格怎么渲染”,而是“文档数据模型、公式计算、协同同步、渲染引擎”这一整串底层问题。适合正在做数据分析平台、低代码应用、企业知识库,或者单纯受够了在react-table和Excel之间来回倒腾的人。

1. 为什么中后台的表格需求,最后都绕不开Univer

1.1 自研表格的尽头是电子表格,电子表格的尽头是协同

很多团队一开始是拒绝引入重量级表格库的,总觉得“需求不复杂,用Table组件就行”。但实际业务里只要用户开始说“我能不能把这一行拖到上面”“我想合并单元格”“我要冻结前两列”“我想按这个条件标红”,问题就不只是前端渲染了。

传统Table组件会暴露出三个根本局限。第一是编辑和展示割裂:有的库支持点击单元格编辑,但公式、批注、筛选、合并单元格这些Excel原生功能基本得从头造。第二是数据模型太浅:业务数据一旦需要行列转置、批量粘贴、跨Sheet引用,普通数组套对象的模型就会越写越别扭。第三是导出Excel永远在补洞:用js-xlsx那一套处理复杂样式、合并单元格、多Sheet导出,每次都能撞出几个新bug。

Univer的思路是:我不给你一个grid组件,我给你一个电子表格运行时。你在这个运行时上写业务逻辑,UI、公式、导入导出、协同都天然具备。这样至少省掉了几万行自研代码。

1.2 为什么不是直接用Luckysheet或Handsontable

市面上已有的开源表格方案并不少,但可以简单分个类:

方案定位协同公式二开体验
Handsontable商业级数据网格靠插件支持有限文档好,授权费不便宜
AG Grid企业级表格/网格需自己做弱适合数据展示,不太像Excel
Luckysheet早期在线表格有思路但维护节奏不稳较多社区资料多,工程体系偏旧
Univer企业级文档套件架构级支持独立公式引擎插件体系清晰,包设计现代

简单说,Handsontable和AG Grid本质上是“表格控件”,它们擅长的是大数据量展示、排序、虚拟滚动,但你要是把用户丢进去做Excel式编辑,体验会立刻露馅。Luckysheet是最接近Univer的竞品,但在代码工程化、插件机制、公式引擎设计上,Univer显然更符合现代前端团队的开发习惯。

有经验的工程师选型时会看一个细节:这个项目有没有把“文档模型”和“渲染层”分开设计。Univer的core、engine-render、engine-formula是完全解耦的。这意味着你可以在不打开网页的情况下跑公式计算,可以在WebWorker里处理数据,也可以把同一套数据模型接到小程序或Native容器上。这种干净的边界,才是它能承载“在线Office”这个想象空间的原因。

1.3 官方Demo和“Univer在线”能给你什么信息

在决定接入之前,建议先去玩一下官方提供的在线Demo。Univer在线版本能直接拖拽行列、合并单元格、玩公式、导入导出Excel,甚至在高分辨率屏幕下看渲染锯齿情况。这个环节最大的价值不是“体验好不好”,而是帮团队确认:交互模型是不是真的朝Excel看齐,临时的崩溃率和无响应情况是否在可接受范围。

我见过不少团队把Univer的Demo发给测试,然后用手机打开,看操作流畅度。确实,移动端适配目前不算它的主推场景,但它能跑通,没有触屏灾难。如果你的用户主要是桌面浏览器,这个方向基本就没有硬伤了。

2. Sheet的基本用法:从初始化到第一个能用的表格

2.1 今天推荐用presets方式初始化

Univer的包拆分很细,早期版本是@univerjs/core、@univerjs/sheets、@univerjs/sheets-ui、@univerjs/engine-formula一个个手动装,像吃火锅时自己调九宫格,灵活但容易搞乱。现在的官方推荐方式是presets预置包,把常见组合都打好包。

以一个React或Vue项目为例,安装依赖后,核心初始化代码大致是这样:

import { Univer } from '@univerjs/presets'; import { createUniverSheet } from '@univerjs/presets/sheets'; import { createUniverFormula } from '@univerjs/presets/formula'; import { createUniverUi } from '@univerjs/presets/ui'; const univer = new Univer(); // 注册UI和Sheet核心能力 createUniverUi(univer); const sheetApi = createUniverSheet(univer); const formulaApi = createUniverFormula(univer); // 创建一个空白工作簿 univer.createUnit(UniverInstanceType.UNIVER_SHEET, { name: '订单数据', });

这段代码跑起来,页面上就会出现一个完整的电子表格,工具栏、单元格、行列头、滚动条全都有。配合一个固定高度的容器DIV,它就是你的在线Excel。

为什么这样设计更合理?因为createUniverSheet返回的API对象会把数据变更、协同消息、公式重算这些偏底层的逻辑统一管理起来。你不用自己拼事件总线,也不用考虑React状态同步,Univer自己就是状态中心。页面组件只要关心容器DOM和按钮触发即可。

2.2 读写单元格和基础样式操作

拿到表格之后,最常用的就是读写单元格。注意Univer的数据API是同步风格的,这在JavaScript项目中非常舒服:

const unit = univer.getActiveUnit(); const workbook = unit as Workbook; const worksheet = workbook.getActiveSheet(); // 写入值 worksheet.getRange(0, 0).setValue('你好,Univer'); // 读取值 const value = worksheet.getRange(0, 0).getValue(); // 获取当前选中的区域 const selection = worksheet.getSelection();

这里要特别提醒:不同小版本的API名有时会从getCell变成getRange,从setContent变成setValue,所以以官方文档为准。核心思想是一致的:先定位Range,再执行读写。Range对象的抽象能力很强,你可以一次框选10行10列,然后批量写值,Excel用户熟悉的“选中一片区域粘贴数据”在Univer里就是这么实现的。

样式相关的操作也在同一条路径里。例如设置黄色底纹、加粗、边框:

const range = worksheet.getRange(0, 0, 4, 4); range.setStyle({ backgroundColor: '#ffcc00', fontWeight: 'bold', border: { color: '#000000', style: 'thin' } });

2.3 Excel导入导出和公式体验:用户最关心的两件事

导入导出是Univer的加分项。导入时直接把Excel文件丢给Univer,它会解析单元格值、公式、格式、数据透视表的一部分能力;导出时基本上能还原你看到的样子。

import { createUniverExportXlsx } from '@univerjs/presets/sheets-export-xlsx'; univer.executeCommand({ id: 'sheet.command.export-xlsx', params: { fileName: '月度报表.xlsx' } });

公式方面,Univer的兼容性做得相当好。你直接在单元格输入=SUM(A1:A10)或者=VLOOKUP(B2, C1:D100, 2, FALSE),它都能算。如果某个公式在Excel里能用但Univer暂时不支持,公式栏会给出错误提示,不会黑屏,也不会算错。这块的可靠性是很多团队敢上生产的原因之一。

我建议第一次接入的时候,把所有常用函数类型拉一个清单,比如数学类、文本类、日期类、查找引用类,再配合业务方的历史报表逐个验证。毕竟公式引擎是无边界的,不可能所有函数都测完,但按清单走能让上线前更有底。

3. 实战集成:接入React项目的完整路径和我踩过的坑

3.1 在Next.js里让Univer正常显示

如果你的项目是Next.js或者Remix这类SSR框架,Univer的接入方式会稍有变化。Univer依赖浏览器DOM、Canvas、ResizeObserver,服务端没有这些东西,所以不能直接import。

正确做法是把Univer包放到客户端动态加载里。在Next.js中,通常是抽一个组件,然后:

'use client'; import dynamic from 'next/dynamic'; const UniverSheet = dynamic( () => import('@/components/UniverOnly'), { ssr: false } ); export default function Page() { return <UniverSheet />; }

这个坑的特点是:报错不稳定。有时你只在Node进程里看到window is not defined,有时直接白屏,控制台没有任何React错误,因为错误发生在模块加载阶段。所以第一步就是要确认Univer相关的import路径全部处于ssr: false的组件范围内。

另外,Univer初始化的容器必须能拿到实际高度。常见错误是父容器高度为0,导致Univer渲染出来是一块空白区域,看起来像“没初始化成功”。排查时先给容器写死一个height: 600px,如果恢复显示,再去查布局层。

3.2 打包体积优化与按需加载

Univer的完整能力很重,如果一把梭把presets全部import进去,首屏bundle会明显变大。实际项目建议做三件事:

  1. 使用Vite或Webpack的manualChunks配置,把@univerjs相关包单独拆出来,不要跟业务代码混在一个vendor chunk里。
  2. 按业务裁剪插件。比如只做数据表格,不导入幻灯片和文档模块;不常用的导入导出格式,等有需求了再动态import。
  3. 公式引擎是最大头,但不是每个项目都需要全量函数。如果业务只是简单统计,可以对Univer的公式函数做白名单式加载。这需要看官方文档里的按需函数注册方式,让能跑通的函数集尽可能精简。

实测下来,如果只是做基础Sheet功能,剔除公式引擎后体积能减少不少。公式引擎虽然好用,但在纯展示型页面里属于“锦上添花”,不做首屏需求。

3.3 样式冲突、国际化与主题定制

Univer自带一套比较完整的UI样式,它的CSS变量都带有--univer-前缀。但很多中后台项目会引入Ant Design或Element Plus,全局会重置button、input、table的元素样式,Univer内部UI很容易被覆盖。

解决办法很简单:给Univer的最外层容器加一个隔离class,然后用CSS选择器限定:

.univer-host { /* 重置可能受全局影响的样式 */ } .univer-host .univer-toolbar button { /* 恢复Univer自己的按钮样式 */ }

如果你用的是CSS Modules或Tailwind,注意Tailwind的Preflight会让所有button的背景色变成透明,Univer的工具栏按钮会“裸奔”。这种情况在文档里不容易遇到,但真实项目里几乎必踩。

主题定制方面,Univer支持暗色和暗黑主题,你可以通过配置项直接切换。企业上主题色通常要跟品牌走,这时候配置主色变量比逐个改组件样式要高效得多。

3.4 版本升级带来的API迁移成本

0.x阶段的Univer发布节奏很快,小版本之间API变化可能不小。刚开始接两个月后升级一个小版本,原来的createUniverSheet变成createUniverSheet的内部结构变化,或者旧的setCellValue被getRange().setValue()取代,都是可能的。

我的建议是:用Univer时固定精确版本号,并把升级当作独立迭代任务,不要随意跑npm update。因为Univer的架构在不断演进,今天的良好实践可能在半年后就变了。固守一份测试过的版本,不会让你落伍,反而能让业务稳定。

4. 深入一点:Univer的协同和公式引擎到底怎么运作

4.1 命令模型与撤销重做

Univer内部几乎所有操作都走命令(Command)。你点工具栏的“加粗”、输入一个值、插入一行,最终都会变成一个Command对象。这样做最大的好处是:撤销重做不需要针对每个功能单独写逻辑,只要记录命令和执行命令的逆操作,就能实现完整的undo/redo。

这个设计对协同同步格外重要。假设两个用户同时编辑一个表格,本地操作马上应用并散发出一条Command或Operation,远端收到后在自己的文档模型上应用同样的操作。由于Univer的数据模型经过设计,这些操作以区间为单位(而不是某个绝对行列坐标),所以并发操作时不容易冲突。

如果要用一个概念来类比,Univer的操作对象更像“给一段文字加粗”“在第10行上方插入一行”这种语义,而不是“把第5行第3列改成42”。这就是它做协同的底气:命令天然支持变换操作,而绝对坐标在并发场景下很容易互踩。

4.2 公式引擎的依赖图与缓存

Univer的公式引擎是独立模块@univerjs/engine-formula。它不直接依赖UI,连线都通过Core模型。当你输入=SUM(A1:A10)时,Univer会:

  1. 解析公式并识别所有依赖的单元格区域;
  2. 在公式依赖图里构建节点和边;
  3. 计算时从叶子节点开始,按依赖顺序求值;
  4. 结果缓存在公式引擎内部,并在引用的单元格变化时只重算受影响的范围。

这个设计的好处是性能稳定。Excel用户如果手动把10行改成1000行数据,Univer不会傻乎乎地把整个工作表重新算一遍,而是精准更新受影响的子图。对于中后台报表这种“数据量可能很大但公式结构固定”的场景,这个机制非常关键。

4.3 协同同步的正确姿势

很多人以为Univer内置好协同服务了,开箱即用。严格说,Univer提供了协同的客户端应用框架和基础网络通信,但具体的后端房间管理、WebSocket服务、权限控制、存储快照都需要你自己实现。这有点像Chrome浏览器的内核和完整浏览器的区别。

我在实际项目里是这样做的:

  • 前端把Univer的Command/Operation统一包装成消息,通过WebSocket发送到协同服务;
  • 后端维护一个文档ID、版本号、操作序列的队列;
  • 客户端每次同步时带上当前版本号,用来做冲突检测;
  • 出现明显冲突时,最简单可靠的做法是锁机制:让用户看到“这行正在被其他人编辑”,而不是强行并发合并。

对于大多数企业内部系统,乐观编辑加局部提示已经够了。真正的全实时高强度多用户Excel协作,需要引入OT变换算法,那是一个严肃的实时协作系统工程,不是前端库能包办的事。Univer帮我们做了一半工作,剩下的是你业务自己的协作语义。

5. 给需要二次开发的团队:插件机制与自定义扩展

5.1 从依赖注入容器理解Univer的骨架

Univer内部用依赖注入容器管理所有模块。因此二次开发的第一个认知是:不要直接去改包源码,那是下策;正确做法是往容器里注册你需要的服务和UI。每一个Univer自带功能,本质上都是某个插件。

这种设计对定制业务的帮助非常直接。假设你需要在工具栏上增加一个“重新格式化本月数据”的按钮,不是去改Univer组件,而是写一个自定义插件,注册按钮并在点击时触发你自己的数据映射逻辑。

5.2 注册自定义按钮和右键菜单

Univer的UI扩展点在@univerjs/ui里。你要做两件事:

  1. 在自己的插件里定位UI插件实例;
  2. 调用它的注册方法添加MenuItem。

整个流程非常简单,核心代码块类似:

import { UniverUiPlugin } from '@univerjs/ui'; const uiPlugin = univer.getPlugin(UniverUiPlugin); uiPlugin.registerMenuItem({ id: 'custom.export-pdf', title: '导出PDF', type: 'button', onClick: () => { // 调用你自己的后端导出服务 } });

注意,不同版本的方法名称可能不同,但思路一以贯之。这种方式能够定制很酷的交互:比如在右键菜单里加“一键生成图表”,在公式栏旁边加“调试模式”,在全屏工具栏里加“同步到我的Dashboard”。

5.3 自定义公式函数

业务报表经常出现“按某某规则计算”的场景。与其让用户套一长串原生函数,不如注册一个公司内部函数。Univer公式引擎允许开发者注册自定义函数,我都是用这套逻辑处理业务算法的。

import { UniverFormulaPlugin } from '@univerjs/sheets-formula'; const formulaPlugin = univer.getPlugin(UniverFormulaPlugin); formulaPlugin.registerFunction('WHOLESALE_PRICE', { calculate: (cost: number, margin: number) => { return cost * (1 + margin); } });

注册完之后,用户直接在单元格里写=WHOLESALE_PRICE(C2, 0.15)就能得到结果。这种自定义能力放在Excel里通常需要VBA宏,而在Univer里就是一段干净的JavaScript代码。它的稳定性、版本管理和测试路径都现代化得多。

如果你有后端数据服务,也可以把公式的rpc调用接到后端,让“单元格输入公式”直接触发内部的定价引擎或风控计算服务。Univer的command数据模型在这里完全能使上劲。

6. 我的最终建议:什么项目适合上Univer

6.1 适合直接上的场景

  • BI数据分析平台:用户需要在线编辑过滤条件、透视表格式、把报表导出成Excel,Univer做前端载体非常合适;
  • 低代码/无代码应用:需要给用户一个“像excel一样拖拽”的建模界面,Univer的协作和公式能力刚好是地基;
  • 企业知识库和数据中台:表格只是文档环境中的一部分,和Doc、Slide配合起来能形成完整的在线Office工作站;
  • 私有化部署要求强烈的政企项目:Univer是开源协议友好的方案,可以本地部署,不受SaaS服务供应商限制。

6.2 不太适合的场景

  • 纯展示型数据表格:如果只是“把SQL结果渲染出来”,不要用Univer,AG Grid的虚拟滚动和排序体验更轻快;
  • 移动端优先的小程序:Univer的重心在桌面浏览器,虽然能跑,但端上适配的时间和收益不成正比;
  • 团队没有前端工程师的情况:Univer的二次开发需要一定的TS水平和工程化能力,不是给一个HTML就能解决的。

6.3 怎么选择版本和保持跟进

如果你决定上手Univer,我建议从当前官方最新稳定版开始,锁定版本号,然后优先跑通一个最小Demo。日常维护方面,多关注官方GitHub的Release notes和架构文档,因为0.x阶段的设计变化还挺多的。但不要因为变化快就观望,相反,你越早用它,团队积累的迁移经验和数据模型理解就越值钱。

就我个人经验而言,在内部系统里拿Univer做数据表格底座,比预期顺利得多。真正需要花时间的不是“跑通”,而是想清楚哪些数据要实时同步、哪些只做单向展示、哪些公式要限制白名单。这些业务决策,比选型本身更决定成败。把Univer当作一个持续的工程资产去经营,它会随着版本迭代慢慢变成你们公司产品体验的一部分。

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

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

立即咨询