☰
从在线Excel到Univer:嵌入式表格、协同编辑与性能排坑实践
2026/11/5 9:41:51 网站建设 项目流程

最近“univer”和“univer在线”一起被搜上热榜的时候,我愣了一下,因为这个项目我已经跟了有一阵子了。刚好两周前,我刚帮团队把一套原本靠Excel人工维护的业务数据,搬进了Univer做成的页面内嵌表格里,从初始化到协同编辑都踩了一轮。这篇就围绕几个问题展开:Univer到底是什么,它和在线Excel有什么区别,怎么在项目里真正跑起来,以及嵌入之后有哪些坑需要避。如果你是做管理后台、数据产品、低代码平台,或者想在官网里给用户一个可以直接录入和计算的表格,Univer很值得认真评估一次。

1. “Univer在线”为什么会被搜:先看它到底解决了什么问题

1.1 我眼里的Univer:不是又一个在线Excel,而是办公能力的基础设施

Univer是一个开源、可扩展、面向Web的办公套件基础框架。目前社区里讨论最多的是它的电子表格能力,也就是Univer Sheets,单元格编辑、公式计算、条件格式、筛选、导入导出、协同编辑这些日常高频功能都已经比较完整。官方规划里,文档和幻灯片也会在统一架构下逐步展开,未来的想象空间是同一套底层能力承载多种办公内容形态。

我之所以强调它是“基础设施”,是因为Univer并不是一个开箱即用的最终产品。它更像一套由数据模型、命令机制、渲染层和插件生态组成的积木。你可以只引入表格能力,不要默认工具栏,不要菜单,甚至不要UI,把它当成一个能编辑、能算、能导出的表格引擎来用;也可以全套引入,得到一套接近在线表格的完整界面。这种取舍自由度,是绝大多数“在线Excel产品”给不了的。

这也解释了为什么很多人搜索“univer在线”。大家直觉上以为这是一个可以替代Excel的网站,但实际上Univer是嵌进你自己的应用里的,不是让你换一个网站去办公。它面向的是开发者、数据产品经理,以及所有想把表格能力变成业务模块的团队。嵌入,才是Univer的核心立场。

1.2 它和WPS/云文档、传统表格组件之间的定位差异

为了便于选型,我把自己比较过的几类方案放在一张表里。这里的对比角度是“我要在自己的系统里嵌入表格能力”,不是终端用户的日常使用习惯:

方案本质嵌入难度数据归属可扩展性最适合的场景
WPS/腾讯文档嵌入在线文档产品低第三方平台低只想分享链接,不关心二次开发
Luckysheet开源表格组件中自己的服务端中快速复刻Excel式界面
Handsontable数据网格组件中自己的服务端中高更专注数据录入和编辑
AG Grid高性能数据网格中高自己的服务端高大数据量展示与操作
xlsx只处理文件的库低自己的服务端低纯解析或生成Excel文件
Univer办公基础框架中自己的服务端高把表格、公式、协同作为业务能力内嵌

这张表本身就能筛掉不少项目。如果只是要导出Excel文件,xlsx足够;要一张好看且性能好的数据网格,AG Grid更直接;但如果用户需要像在Excel里那样输入数据、拖拽填充、写公式、做条件格式,未来还要加多人协同,并且这些能力必须嵌进自己的管理后台或低代码平台,那Univer的路线就更对口。

还有一个容易被低估的点:因为Univer的数据模型与渲染层是分离的,它在跨端协同和业务扩展上的思路,比传统的表格组件更接近办公套件。你对单元格做的每一次修改,走的都是同一套命令逻辑。这个设计不仅支撑了撤销重做,也为协同编辑、AI自动化操作、业务规则校验留下了干净的入口。下一部分我会仔细拆这套机制。

2. Univer的核心架构拆解:数据模型、命令总线与插件系统

2.1 数据模型:一份工作簿数据,多种表现形态

如果你接触过Excel的COM对象模型,会对Workbook、Worksheet、Range这套概念非常熟悉。Univer的数据模型也是类似思路:一个Univer实例里可以有一个或多个工作簿,每个工作簿下面挂多个工作表,每个工作表由单元格组成,单元格里可以放值、样式、富文本和公式。

这套模型的优点主要有两个。第一,它独立于渲染界面。Univer表格在页面上画出来的东西,本质上是对这份数据的某种投影。你可以换一套主题、换一种布局,只要数据模型不变,表格内容就能保持一致。第二,它非常适合程序化操作。后端返回数据后,前端把数据填充进单元格;用户修改某个单元格,模型会通知对应视图去更新,而不是把整个表格重新渲染一遍。

我曾见过有些团队用表格组件时,直接从DOM里把值读出来存库。这种做法在简单场景下没问题,但一旦涉及公式、合并单元格、样式还原、撤销重做,就会非常痛苦。Univer从底层就是围绕“单元格数据加样式加公式”来设计的,业务方只需要把业务字段映射成单元格,就能得到比较完整的表格数据处理能力。它不是为了“画一个表格”,而是为了“管理一份表格数据”。

2.2 命令总线:把每一次操作变成可以回放的事件

Univer里几乎每一个用户操作,最终都会变成一个命令(Command)。命令是很薄的一个对象,包含类型、名称、必要参数。执行时会走统一的调度器,先做校验,再执行,再发布事件。比如用户输入了一个值,就会有一条“设置单元格值”的命令被派发;这条命令改动的是数据模型,而不是直接操作DOM。

这个设计的价值,尤其体现在可撤销、重做、协同编辑、操作审计和AI辅助操作上。传统表格组件要实现撤销,需要自己保存单元格快照;而Univer的命令天然自带反向操作,只要把命令执行栈维护好,撤销和重做就能做得比较干净。协同也是同理:要让多人同时编辑同一份表格,服务端收到的不是“某人在某行某列写了个值”这种零散消息,而是一串带顺序和依赖关系的命令流。在此基础上做操作变换,逻辑会统一很多。

我打一个生活化的比方:普通表格组件像一块白板,大家各写各的,改完拍照保存;Univer更像一套带流水账的账本,每一笔改动都记在账上。有了流水账,才谈得上回放、对账和多人协作。这也是Univer能支撑复杂办公场景的根因。

2.3 插件机制:核心保持精简,能力靠插件挂载

Univer的项目结构里,核心包只负责最基本的数据模型和生命周期调度,表格功能、UI渲染、公式引擎、协同引擎、导入导出等能力都通过插件来挂载。你初始化时注册了什么插件,页面里就有什么能力。

这样做的好处非常直观。第一,按需加载。如果只做数据录入,可以不注册公式插件和协同插件,减少不少资源开销。第二,低成本定制。想隐藏某个菜单、替换某个按钮的文案、在保存前校验某列数据,都可以通过插件暴露的扩展点来介入。第三,业务可以横向扩展。比如在单元格右键菜单里加一个“调用内部接口查询库存”的按钮,这在普通在线表格产品里很难实现,在Univer里却是一个常规操作。

我在做第一个Univer项目时体会最深的是,很多功能被抽象成了可组合的模块。这种架构给团队带来的不只是灵活性,更是可维护性。新需求来了,先判断它属于数据模型、命令、UI还是渲染层,再去对应的地方扩展,路线非常清晰,不会所有逻辑都堆在一个巨型文件里。

3. 把Univer嵌入自己项目的完整过程:从空页面到可编辑表格

3.1 初始化一个最小工程:先别急着写业务代码

如果你想快速体验效果,用官方示例里的CDN方式最省事;如果要放在自己的代码里长期维护,我建议用Vite配合TypeScript工程来搭。先建立一个干净的前端工程,然后安装Univer的相关依赖。

Univer目前采用多包发布策略,具体包名和版本以官方文档为准。社区里最常用的是preset预设,相当于把一套完整表格能力预打包好,初始化时直接注册即可。这一步看上去很简单,实际最容易踩到版本坑。我第一次接入时,照着老教程引入了旧插件,结果新版改成preset之后怎么都跑不起来。所以这里我强烈建议:不要凭记忆写包名,先打开当前版本官方仓库的README,按它给的方式安装。这个习惯能省掉很多不必要的排查时间。

3.2 注册插件、设置容器和多语言

核心工作就是把Univer初始化、注册表格预设、指定HTML容器。我贴一个当前社区里比较常见的写法,但请以你实际安装版本的文档为准:

import { Univer } from '@univerjs/core'; import { UniverPresetSheets } from '@univerjs/preset-sheets'; import '@univerjs/preset-sheets/themes/green.css'; const univer = new Univer(); univer.registerPlugin(UniverPresetSheets, { container: 'app', locale: 'zhCN', });

这里有三个细节值得单独拿出来说。

第一,容器元素必须要有明确高度。很多人把Univer放在一个默认高度为0的div里,页面上看起来一片空白,还以为是加载失败。记得给html、body或外层容器设置height: 100%,或者直接给固定像素高度。

第二,主题CSS一定要引入。Univer的UI不是纯函数式渲染,它有一套接近桌面办公软件的界面样式。如果漏了主题样式,你会发现按钮位置错乱、布局完全不对。

第三,多语言要在配置里指定。Univer自带不少语言包,初始化时把locale设成zhCN或enUS即可。不要等界面出现英文再逐条去翻文案,初始化时顺手处理掉,后面省很多事。

3.3 把真实业务数据塞进表格:构造工作簿JSON而不是逐格赋值

初始化完成之后,Univer默认会创建一张空白工作表。但真实业务里更常见的是:从后端拉一份列表数据,让它以表格形式呈现给用户编辑,编辑完再导出成Excel或者提交到服务端。

Univer的工作簿描述结构非常接近Excel的JSON模型。你可以在创建时传入工作簿数据,包含工作表名称、行列数量、单元格的值和样式;也可以在后端生成一份JSON,前端直接解析填充。用代码描述大致是这样:

const univer = new Univer(); univer.registerPlugin(UniverPresetSheets, { container: 'app', locale: 'zhCN', }); const unit = univer.createUnit({ type: UniverInstanceType.SHEET, name: '订单明细', sheets: [ { id: 'sheet-01', name: '订单', rowCount: 100, columnCount: 10, cellData: { 0: { 0: { v: '订单号' }, 1: { v: '客户' }, 2: { v: '金额' } }, 1: { 0: { v: 'SO-1001' }, 1: { v: '张三' }, 2: { v: 1280.5 } }, }, }, ], });

同样需要提醒,具体方法名和数据结构可能随版本变化,但思路是稳定的:表格是描述出来的,不是一行一行插出来的。把业务数据映射成cellData之后,Univer会立刻渲染出对应内容。用户在前端改动后,你再监听Univer发出的变更事件,把数据同步回后端;需要下载Excel时,再调用它提供的导入导出插件。

这里最值得强调的一点是:不要用循环“逐格赋值”的方式批量填充数据。一次性构造好工作簿JSON再让Univer加载,性能会好很多。我测试过一次批量导入两千行数据,用逐格赋值时页面几乎卡死;改成整体JSON加载后,耗时降到了完全可接受的范围。

4. 公式引擎与多人协同:这两个“黑盒”到底怎么运转

4.1 公式不只是字符串解析:它维护了一张依赖图

Univer自带公式引擎,支持大量常用函数,并且一直在扩充。公式引擎最核心的部分是依赖图:当某个单元格的值变化时,引擎需要找出哪些公式引用了它,再按依赖顺序重新计算,而不是把整个表格扫一遍。

听起来很基础,实际做起来有不少细节。比如跨工作表引用,Sheet1!A1 + Sheet2!A1这种公式要跨表维护依赖;又比如循环引用,A1引用了B1,B1又引用A1,引擎必须能识别并给出提示,而不是陷入死循环。Univer把这些常见场景封装好了,你在业务里只需要注册公式插件,然后像普通表格一样写=SUM(A1:A10)就行。

如果你有自定义公式的需求,比如要调用内部接口查询某个业务指标,Univer也提供了注册自定义函数的入口。注册之后,用户在表格里可以像使用普通函数一样调用它。这项能力对数据密集型产品很有用,等于把一部分业务计算规则开放给了业务人员,他们可以直接在表格里写“=库存查询(B2)”这种公式。

有一点需要提前适应:公式计算有异步可能,尤其是自定义函数要请求接口时,单元格会先显示等待状态,等结果返回后才更新。这种体验和原生Excel不同,但更符合Web应用面对的实际情况。如果未来要做服务端计算,还要考虑公式引擎在Node端如何对接数据源的问题,这是一个值得单独开一个项目来做的方向。

4.2 协同编辑真正要做的是同步命令流,不是同步整张表

多人同时编辑一张表格,最容易想到的粗暴做法是每秒钟把整张表同步给所有人。数据量小的时候勉强能跑,一旦冲突变多,数据就乱得没法收拾。Univer的协同思路选择了另一条路:每个用户的操作被转换成命令,像流水账一样发给服务端,服务端做操作变换后广播给其他客户端,其他客户端再用相同的顺序执行这些命令。

这样设计的好处是,不同用户对同一个单元格的修改,可以通过操作变换合并成确定性的结果,而不是“后保存的覆盖先保存的”。同时,因为协同同步的是命令而不是最终值,新加入的成员也可以用最近的操作流重建表格状态,不需要频繁全量拉取。

如果你的目标不是做多人协作,只是想保存用户编辑结果,那不需要把操作变换的细节都搞明白。但如果确实想做协同,我的建议是不要自己去同步单元格值,那条路走到后面会遇到数不清的冲突问题。直接基于Univer的命令机制,让服务端转发命令流,让前端和协作共用同一套命令链路,这样维护成本会低很多。

5. 嵌入后的性能与兼容性排坑:我实际遇到的三类问题

5.1 大数据量下的滚动与输入卡顿

我第一批测试数据只有几千行,Univer跑得很稳定。当我塞入二十万行带样式的虚拟数据时,卡顿感就非常明显了。这不见得是Univer的问题,而是“一次性在前端加载太多数据”的典型症状。

对策主要有三种。一是按需加载,先把当前可视区域的数据拉到前端,滚动时再请求后续数据。二是减少高开销的富文本和复杂样式,纯文本和纯数值单元格的渲染成本要低得多。三是把大批量公式计算放到独立线程或者服务端,避免公式计算全部挤在UI主线程上,影响用户的输入手感。如果你的产品定位是“轻编辑”而不是“要打开几百万行Excel”,把数据量控制在几万行以内,同时做好首屏加载提示,体验是完全过得去的。

5.2 样式冲突、布局塌陷与资源路径问题

Univer的UI复用了一套类似桌面办公软件的组件体系,会往页面上注入大量样式。如果你的项目正好用了比较激进的全局CSS重置,把所有的button、input、div默认样式都改了,很容易让Univer的工具栏看起来和周围格格不入。遇到这种问题,先检查主题CSS是否正确引入,再排查是不是全局选择器污染了Univer内部组件。

另一个容易忽略的是资源路径。如果用户访问的地址在子路径下,比如/admin/sheets/,Univer的静态资源和多语言包可能因为相对路径加载失败,导致界面缺样式、缺图标。这类问题大多出现在部署环节,不只是代码里import一下就完事,还要同步处理构建工具的基础路径配置或服务端静态资源代理。

5.3 版本升级带来的破坏性变更:锁版本加小步升级

Univer目前还在快速迭代期,API调整频率不低。我第一次接入后隔了一个多月再升级,发现原来注册插件的写法变了,主题文件路径也变了。这不是Univer独有的问题,所有活跃开源项目都这样,但对接业务项目时确实会造成额外压力。

我的处理办法很朴素:在package.json里锁住版本号,不用浮动版本;每次升级前先读官方CHANGELOG,重点关注破坏性变更;升级后跑一遍核心用例,比如创建表格、填写数据、保存数据、导出Excel,确认主链路没问题再交付。不要为了追求新功能而频繁升级,稳定性永远排在第一位。

如果你已经决定深度使用Univer,我建议把初始化、数据填充、导出、监听这些操作统一封装在自己的适配层里。未来Univer再升级,只需要改适配层,业务代码不用跟着大改。这是我在多个开源组件接入里总结出的最实用的经验,不只是对Univer适用,对任何仍在快速演进的前端基础组件都适用。

最后再分享一个小技巧:用Univer调试的时候,多留意浏览器开发者工具的控制台。很多问题并不是页面白屏或按钮失灵,而是会在控制台直接输出错误信息,里面往往直接告诉你插件没注册、主题没引入还是版本不匹配。先看控制台,再去翻文档和源码,比盲目改配置有效得多。

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

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

立即咨询