最近我把公司后台的一个报表模块重写了,需求描述得特别简单:“页面上能填表,能算数,导成 Excel 发给别人就行。”等真动手才发现,在线表格这个需求一点都不“简单”。输入、格式、公式、校验、合并单元格、冻结窗格,哪一个都得花精力;更麻烦的是,万一要多人同时填同一张表,还得保证最后导出结果不冲突。我查了一圈,最后停留在univer这个开源项目上,又顺着翻了好几天“univer 在线”相关的资料和社区讨论。这篇就把我实际接入的整个过程、关键设计点和踩过的坑完整记录一下,给做前端集成、自建在线表格场景的朋友一个参考。
先说结论:univer 不是那种只能“画一个表格”的组件,它更像是一套可嵌入、可扩展、可协同的办公套件底座。它以 TypeScript 编写,Apache 2.0 协议开源,覆盖电子表格、文档、幻灯片三个方向。目前 GitHub 上热度很高,社区也活跃。如果你准备做低代码平台、数据填报系统、内部运营工具,或者想在 SaaS 产品里加入“在线编辑 Excel”的能力,这篇内容应该能帮你少走不少弯路。
1. Univer 是什么:从一个开源表格库聊起
1.1 为什么我盯上了 univer,而不是继续用老方案
做前端的同学应该都经历过这个场景。客户或者业务方说“我想要一个像 Excel 一样能编辑的表格”,结果你翻遍网上方案,发现每条路都不太顺畅。
纯 DOM 表格控件,比如很早的 jqGrid、DataTables,本质上还是“展示型表格”。做做列排序、行选择还行,真要支持公式、单元格合并、下拉校验、撤销重做,就要自己写一大堆逻辑。Canvas 方案的表格库能扛大数据量,但往往编辑能力弱,输入框、弹层这些交互实现起来很割裂。商业表格控件功能倒是全,但授权费不低,而且集成限制多,代码层也不好改。
我第一眼关注 univer,是因为它的定位直接跳过了“表格控件”这个层面,它是奔着“办公套件”去的。GitHub 上项目仓库名就叫 univer,社区里普遍认为它是 Luckysheet 思路的延续和重构。但我看下来,它在工程化、模块拆分和扩展性上做得明显更深,不是简单地把旧代码换个壳。
如果你搜“univer 在线”,会发现很多人在找“浏览器里直接用的在线表格编辑器”或者“可以嵌进自己项目的在线编辑组件”。这正是 univer 的主要应用方式:以 npm 包形式集成到 Web 应用里,UI 交互、公式计算、渲染都替你处理好,你需要做的只是准备一个容器节点,然后初始化实例。
1.2 架构底子:为什么它不是一个“又一个表格控件”
我读完 univer 源码结构后的最大感受,不是某个表格功能写得多巧妙,而是它把“数据”和“渲染”拆得特别干净。
内部对文档模型的定义基本遵循 Excel 心智:Workbook 下挂 Worksheet,Worksheet 里排列 Cell,单元格上再挂样式、公式、校验规则。行高、列宽、合并区间、冻结窗格这些信息,全部落在数据模型里,而不是靠 DOM 节点硬撑。
这套模型再往上,是一套命令系统。所有操作,比如编辑单元格、插入行、删除列,都封装成 Command。命令系统听起来玄乎,实际价值非常大。它给每一次操作都留下了“记录”,可以撤销、可以重做,也能把操作步骤同步给其它端。这正好就是在线协同的基础,后面我会专门展开。
渲染端用的是自研 Canvas 引擎,不是每个单元格生成一个 DOM 元素。Canvas 按可视区域绘制内容,滚动时才动态更新画布。所以几十万行数据不至于把浏览器拖垮,这也是 univer 敢提“大数据量流畅编辑”的底气。
同时整个项目大量使用依赖注入和插件注册机制。核心的编辑、渲染、公式是独立引擎包,导入导出、图表、协同等功能以插件形式挂载。你想加一个自定义按钮、一个右键菜单项、一套自定义公式,都不用去改源码,往插件注册中心里挂一个实现就行。
提示:如果你想快速理解 univer 的分层,记住四个词就够了:文档模型、命令系统、Canvas 渲染、插件机制。后面所有高级玩法,都是围绕这四个词展开的。
2. 核心功能盘点:Univer 到底能干什么
2.1 表格基础能力:编辑、公式、条件格式、数据校验
从实际使用看,univer 对 Excel 的高频操作覆盖得相当完整。单元格编辑、点击选中、拖拽填充、合并单元格、调整行高列宽、冻结窗格、排序筛选,这些基础操作开箱即用。样式方面也跟 Excel 习惯对齐,字体、字号、加粗、底色、边框、对齐方式、数字格式都有。
给我印象最深的是公式引擎。它作为独立模块存在,官方内置了文本、逻辑、日期、统计、查找引用、数学、财务等常用函数类别,像 SUM、VLOOKUP、IF、COUNTIF 这些高频函数都没问题。公式解析和计算是支持跨工作表引用的,比如在 Sheet1 的单元格里直接写=Sheet2!A1*2。最实用的地方是,公式引擎支持注册自定义函数,业务上完全可以把复杂的计算逻辑变成表格里的一个公式。
条件格式、数据验证也都在能力范围内。条件格式可以对满足规则的单元格自动套用颜色,比如“库存低于阈值标红”,对运营类报表非常有用。数据验证则可以做成下拉选择、数字范围校验、文本长度限制。我做过一个填报系统,其中“状态”列要求只能选“待处理/处理中/已完成”,给单元格配置一个下拉数据验证后,填表的同事再也不会随手打出乱七八糟的文案。
2.2 在线协同:多人同编的核心卖点
“univer 在线”这个热词背后,其实藏着一个常见需求:多个同事打开同一张表,一起填数,互相能看到更新。
univer 的协同能力建立在前面提到的命令系统之上。编辑动作转化成操作指令,客户端把指令发送给服务端,服务端做排序和分发,其它客户端接收后回放,最终大家的数据状态保持一致。这种基于操作日志的方案,天然适合做增量同步,也能记录编辑历史。
但我要提醒一下:univer 提供的是协同的客户端 SDK 和协议基础,服务端部分需要自己搭建或者做二次封装。如果你的项目正好就是“多人编辑同一张工作表”的需求,那非常合适;如果只是单人在线填表,那协同模块可以先不启用,避免过度设计。
2.3 跨端与二次开发:一个底座,多种场景
univer 的跨端体现在“同一套核心,不同容器”。在 Web 端可以用 Div 做容器;在桌面端可以套 Electron;移动端因为已经做了 Canvas 渲染和触摸事件抽象,也有落地的可能。对大多数团队来说,最实际的用法还是 “Web 应用内嵌”。
二次开发这件事,univer 给到的自由度很高。你可以新增工具栏按钮,可以自定义右键菜单,可以监听选择区域变化、单元格值变更、工作表切换等事件,也可以替换默认的图标和主题色。我把 univer 接入后台系统之后,甲方提的很多“小需求”基本都在扩展层解决了,没有动到底层源码,升级依赖的时候也能省不少事。
3. 实操过程:把 Univer 嵌入你的前端项目
3.1 环境准备与依赖安装
我建议在 Node.js 18 以上的环境下操作,包管理器用 npm 或 pnpm 都行。univer 是标准 npm 包分发,没有私有源要求,直接装就能用。
我项目里用的是 React + TypeScript + Vite,主要依赖如下:
npm install @univerjs/presets @univerjs/presets-sheet@univerjs/presets是快速预设包,内置了核心、UI、渲染、公式等常用模块的组合,适合第一次上手。实际开发中,如果你需要更细粒度地控制依赖,可以按官方文档拆分成@univerjs/core、@univerjs/sheets、@univerjs/sheets-ui、@univerjs/engine-formula、@univerjs/engine-render等独立包。
另外,样式文件需要手动引入,不然界面会裸奔。一般是在入口文件里加一行 CSS 导入,具体路径以你安装的包内声明为准。
3.2 最小可用示例:React + TypeScript
用一个最简单的组件演示接入流程。先准备一个固定高度的容器节点,再在useEffect里创建 univer 实例,销毁时释放资源。
import { useEffect, useRef } from 'react'; import { Univer } from '@univerjs/presets'; import { createUniverSheet } from '@univerjs/presets-sheet'; import '@univerjs/presets/lib/style.css'; function UniverDemo() { const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (!containerRef.current) return; const univer = new Univer({ uniAuto: true, locale: 'zhCN', }); univer.createUnit( createUniverSheet({ container: containerRef.current, }) ); return () => { univer.dispose(); }; }, []); return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />; } export default UniverDemo;这段代码跑起来之后,页面里就会出现一个完整的在线表格,能点、能编辑、能输公式。new Univer()创建一个应用实例,createUnit则指定创建什么类型的文档单元,我们这里创建的是 Sheet 表格。
生命周期这块要格外注意。React 18 的 StrictMode 在开发环境下会模拟组件挂载、卸载、再挂载的过程,如果你在useEffect里创建实例,但清理函数没有正确调用dispose(),就会出现两个表格实例叠加、事件重复绑定之类的诡异问题。我一开始偷懒,没有写 cleanup,结果切页面再回来时控制台报了一堆警告。老老实实加上资源释放,世界清净了。
3.3 导入导出:把 xlsx 接进来
在线表格如果只能看不能导入导出,那价值直接砍半。univer 官方提供了导入导出插件,一般叫@univerjs/sheets-import-xlsx和@univerjs/sheets-export-xlsx。
安装并注册对应插件后,工具栏会直接出现导入、导出按钮。导入支持本地选择.xlsx文件,读取后自动渲染成表格;导出则把当前工作簿内容打包成.xlsx文件下载。
这里提醒一点:导入导出插件和预设包之间的版本必须匹配。univer 迭代速度很快,不同大版本之间的插件注册方式可能发生变化。我第一次装的时候,没有控制版本,预设包是 0.x,导入插件已经切到 1.x,结果一注册就报错。后来统一锁定到同一版本,问题才解决。升级时务必先看官方 changelog。
如果你需要在导入后做一些数据清洗,比如统计导入的行数、读取特定单元格内容,可以通过拿到工作簿实例后的 API 进行操作。这里的核心是:导入动作结束后,工作表数据已经完整落到 univer 的数据模型里,你可以像操作普通工作表一样读取它。
3.4 接入现有工程时的几个关键配置
按我实际使用下来的经验,有三个点值得提前确认。
第一,容器必须有确定高度。univer 渲染时会读取容器尺寸,如果容器高度为 0,表格区域会画不出来,页面上一片空白。给外层包一个min-height或者固定高度,是最稳妥的做法。
第二,如果项目里已经存在全局 CSS 重置,比如* { margin: 0; padding: 0 },要留意是否影响了 univer 的内部 UI 组件。必要时可以把 univer 挂载的容器节点单独隔离一个 CSS 作用域,避免样式互相污染。
第三,如果你用的是 Vite,一般不需要特殊 loader;如果是老 Webpack 工程,注意 CSS 和静态资源的处理规则。univer 内置图标等资源会走正常打包流程,不要把 node_modules 里的处理规则一刀切禁掉。
4. 进阶玩法:让 Univer 跟随业务“定制”起来
4.1 自定义函数,让公式接管业务逻辑
公式是表格的灵魂,而自定义函数是把业务逻辑塞进表格最快的方式。
举个例子。运营团队在表格里填了一列“今日新增用户数”和一列“次日留存用户数”,想直接算出留存率。与其教他们用=B2/A2再去设置百分比格式,不如注册一个自定义函数=RETENTION(B2, A2),函数内部可以处理除零、比例转换、保留小数。
自定义函数的基本思路是注册一个计算实现,入参是公式里的单元格引用或数值,返回结果。注册完成后,表格单元格里就能像普通函数一样使用。这类能力特别适合财务计算、金融费率、统计分析等场景。把计算规则固化到公式里之后,业务方自己也愿意多用,因为“在 Excel 里怎么算,在系统里也怎么算”。
4.2 数据联动:前端状态与表格互相驱动
在线表格很少是一座孤岛。最常见的一种玩法是:左侧是表单,右侧是表格,表单提交后表格自动追加一行;或者反过来,表格里改了数据,页面其它统计卡片实时刷新。
univer 提供了事件监听机制,可以监听单元格值变更、选区变化、工作表激活等事件。比如监听值变更后,把当前工作表全部数据读出来,同步给 React 的 state,这样表格之外的元素可以跟着更新。反向操作也支持,你可以拿到工作表和单元格对象,调用 API 给指定单元格赋值,表格内容会立即重绘。
我在项目里就是这么做的:填表人切到“已完成”状态后,页面顶部统计卡片自动更新完成数量;同时外部下拉框切换月份时,表格数据整体替换。整个过程不需要业务方手动点刷新,体验顺滑很多。
- 表格 -> 外部:监听值变更事件,把最新单元格值推送到 state。
- 外部 -> 表格:调用实例 API,定位单元格并写入新值。
- 批量更新:一次性写入多行数据时,优先用批量接口,避免逐格更新导致频繁重绘。
4.3 主题与国际化
univer 默认界面是英文,但支持通过初始化参数改成中文,我这里用的就是locale: 'zhCN'。如果你做的是面向国内用户的内部系统,这一行配置基本必加。
主题定制方面,可以调整品牌色、高亮色、边框颜色等变量。比如把选中单元格的默认蓝色改成企业的主色,改动量并不大,但会给用户一种“这系统是我们自家做的”的感觉。对 To B 产品来说,这个细节挺加分。
5. 常见问题与排查技巧实录
5.1 我踩过的坑:容器、样式、生命周期
整理一个速查表,都是我在实际集成中遇到过的高频问题,按这个表排查,大部分情况能直接定位。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 页面空白,表格没渲染 | 容器高度为 0 或容器不存在 | 给容器固定高度,并在 DOM 挂载后再初始化 |
| 界面没有样式,按钮错位 | 未导入预设 CSS | 在入口引入 univer 的样式文件 |
| React 开发模式出现多个实例 | StrictMode 导致 effect 重复执行 | 在清理函数里调用 dispose |
| 导入插件注册报错 | 插件版本与核心版本不匹配 | 统一锁定同一版本,升级前查 changelog |
| 公式不计算,显示原文 | 公式引擎未正确加载 | 确认预设包是否包含公式引擎,或手动注册 |
| 数据量大时滚动掉帧 | 可视区域外单元格过多 | 拆分工作表,或按需加载数据 |
5.2 数据与公式的同步注意点
公式计算时机是个容易忽略的问题。univer 的公式引擎会在单元格值变化后进行重算,但如果你一次性通过脚本批量写入大量单元格,不建议逐格赋值,那样会导致频繁重算和性能波动。合理做法是先暂停公式计算,批量写入完成后再恢复并触发一次整体重算。
数据格式也要留意。从 xlsx 导入的数字单元格,读取出来的值可能和 UI 显示的不完全一致。比如日期在 Excel 里本质是序列化数值,导入导出时建议在业务层明确日期格式规范,避免“表格里显示 2024-01-05,导出后变成 45296”这种事故。
5.3 Excel 兼容性的边界
虽然 univer 的兼容性已经做得很不错,但它并不是要把浏览器变成一个 1:1 复刻的 Excel。对于基本的数据编辑、公式、格式、表格、图表,它都能应对;但 VBA 宏、复杂的透视表布局、部分高级图表类型、加密工作簿、超复杂的条件格式规则,就不要指望完全兼容了。
我的原则是:上线前必须拿真实业务文件做一轮兼容性测试。导出一份,再导回,对比数据是否一致。遇到 univer 暂不支持的格式,提前在产品文档里说明,或在前端做降级处理,比如提示用户“该文件包含宏,暂不支持在线编辑”。
6. 最后再分享一点个人选择经验
如果让我给一个选型建议:如果你的需求只是“把 Excel 数据展示在页面上”,那 univer 可能太重了,一个原生表格加 CSS 就能搞定。但如果需求是“在网页里像 Excel 一样编辑、计算、校验、协同、导入导出”,那 univer 是目前开源阵营里综合完成度很高的一条路。
我最初选定它的时候,也担心过项目迭代快、文档不够完善的问题。后来发现,只要把版本锁死,核心模块的变化其实是可控的。真正花时间的不是集成,而是理解它的事件流和数据模型。好在这部分一旦想通,做起来会非常顺手。
最后顺手分享一个小技巧:在正式功能开发前,我建议你先花一个下午把最小 Demo 跑通,再导入一份你们业务里最复杂的 Excel 文件,试试公式、格式、数据验证。这个动作能在很短时间内告诉你这个项目到底适不适合你,比看一百页文档都管用。