前段时间把一个在线表格能力接到了我们内部的数据管理后台里,选型过程没有太多纠结,因为 Univer 这个名字我惦记挺长时间了。当时搜项目时满屏都是 "univer"、"univer在线" 这类热词,但真正用过的人其实不多。我花了两周时间从零集成、调通公式、跑完协作流程,中间踩了不少坑,也把这个开源表格引擎的底子摸了个大概。今天这篇就从我的实际使用体验出发,聊聊 Univer 是什么、能做什么、怎么集成到自己的项目里,以及那些文档里不会写但你必须知道的细节,希望能给正在调研在线表格方案的朋友一点参考。
1. 先搞清楚 Univer 到底是什么
1.1 一个标题背后的真实身份
很多人第一次看到 "univer" 这个名字,会以为是某个在线教育或者学习平台,但实际上它做的是办公套件底层能力。用一句最直白的话概括:Univer 是一个基于 Web 的技术框架,专门用来搭建类似于在线 Excel、在线文档这样的产品功能。它不是直接给你一个写好的 "XX在线表格" 网站让你去用,而是给你一套可嵌入、可扩展、可改造的表格内核,让你在自己的网页应用里复制出一块功能丰富的电子表格区域。
我最初是在 GitHub 上看到它的,仓库名是 dream-num/univer,中文社区叫它 "统一办公套件" 或 "Univer Sheets"。它的定位非常明确:面向开发者,解决 "在浏览器里做一套像 Excel 的交互体验" 这件事。Univer 在在线表格场景下覆盖了大部分核心需求:单元格编辑、公式计算、样式设置、条件格式、图表、数据透视表、批注、查找替换、撤销重做等等,几乎可以把它看作一个开源的 "前端版 Excel 引擎"。
它的核心亮点有三个:一是纯前端渲染,底层基于 Canvas 绘制表格区域,交互流畅度比传统 DOM 表格高很多;二是插件化架构,各种能力像积木一样按需组装;三是为在线协作预留了完整的数据链路,可以做到多人同时编辑一张表格并同步状态。
1.2 为什么它适合做 "在线表格引擎"
我用过 Web 页面上常见的几种表格方案。第一种是纯展示型表格,比如用 HTML table 或者 DataGrid 之类的组件,这种方案只适合看数据,做不了复杂编辑。第二种是在线表单,能编辑但逐格输入体验很差,公式、行列拖拽、批量填充这些桌面端表格的 "肌肉记忆" 完全不存在。第三种是引入 Excel 控件,比如 Office 的 Web 版或者一些商业组件,功能确实完整,但要么要付费、要么封闭、要么嵌入时只能整页加载,很难和自建系统做深度打通。
Univer 走的是中间路线:把表格引擎做得足够专业,同时又保持开源和可自定义的灵活性。它默认提供类似 Excel 的多标签页工作簿结构,底部有 Sheet 标签,顶部有工具栏,右键菜单、拖拽填充、列头筛选这些细节都有。开发者拿到的是一个 "会动的表格内核",而不是一张静态截图。更重要的是,它把数据模型和界面渲染拆开了,这意味着你可以把 Univer 表格当作一个可视化编辑组件,背后和你的业务数据实时同步。
对于 "univer在线" 这种场景,它的意义就出来了:你不需要从零去做一套在线 Excel,只需要把 Univer 嵌入到你的网页里,接上后端接口和 WebSocket,一个具备多人协作能力的在线表格模块就立起来了。这也是我最终选择它的核心原因:省去底层造轮子的时间,把精力放在和业务打通上。
1.3 适合谁用,解决什么问题
如果按人群来划分,我觉得 Univer 最适合这几类项目:
- 企业内部数据管理后台,需要让运营、财务、采购等角色直接在网页里编辑数据表,而不是下载 Excel 再上传。
- SaaS 产品需要向用户提供 "在线表格" 功能,比如项目管理工具里的工时表、排期表,数据分析工具里的数据集预览。
- 低代码/零代码平台,希望给用户提供像 Excel 一样可拖拽、可写公式的配置界面。
- 个人开发者想做一套轻量级的数据工具,又不想从头实现行列模型、公式解析、单元格渲染这些复杂算法。
它解决的核心问题就是 "在网页里用表格的方式处理数据"。过去你会发现,只要涉及复杂的表格编辑,前端实现总是很痛苦:单元格合并要算坐标、内容编辑要处理代理事件、撤销重做要维护状态栈、公式解析要自己写词法语法。而 Univer 把这些都替你完成了,你只要调用 API 去操作工作簿数据就行了。
当然,它不是万能的。如果你的需求只是展示一个两层表头的明细列表,用 DataGrid 更轻量;如果你需要完整的宏录制、VBA 脚本支持,那还是老老实实用 Office 桌面版。Univer 的舒适区是 "在线编辑数据",这一点要想清楚再用。
2. 技术选型:为什么我把 Univer 放进项目
2.1 对比传统表格组件的差异
在接触 Univer 之前,我团队里有人建议直接用开源的 x-spreadsheet,也有建议用 Luckysheet,都试过。那次对比让我印象很深,因为同样是开源表格组件,它们的定位完全不一样。
x-spreadsheet 大概两年前还算流行,提供基础的单元格编辑、公式、合并单元格,界面像是简化版的 Excel。但它的问题也很明显:代码维护节奏慢,社区更新不活跃,扩展性比较弱,想加一个自定义工具栏按钮都比较费劲。Luckysheet 功能明显更丰富,文档也有中文,但它的核心库体量偏大,而且整个架构设计偏老,依赖的一些底层库在现代前端工程里集成时需要做不少兼容处理。
Univer 给我的感觉是设计上更现代化。它用 TypeScript 重写了全部核心,类型提示友好;在架构层面强调 "内核 + 插件" 的分离,公式、排序、筛选、数据透视这些能力都被设计成独立插件,你可以只加载自己需要的部分。这一点对性能优化特别重要,因为不是所有部署场景都需要全套能力。
另外,Univer 的渲染层是基于 Canvas 的。传统 DOM 表格在单元格超过几千个时,浏览器渲染就会明显掉帧;Canvas 则通过自绘方式只更新变化区域,滚动和缩放时性能表现稳定很多。我后来测试过一个 50 行 20 列、总共 1000 个单元格的工作簿,在 Univer 里操作非常跟手,而在纯 DOM 方案里输入延迟已经能感觉到了。
2.2 Univer 的架构与核心模块
Univer 的架构可以说是一种典型的 "微内核" 设计。它由一个核心运行时和多个业务模块组成,运行时负责管理依赖注入容器、配置服务、生命周期和命令系统。所有的业务能力都以插件形式存在,比如一打开工作簿,它实际上是在核心之上加载了 Sheets 插件、渲染引擎插件、公式插件、UI 插件等。
对这种设计,我用一个生活化的类比来解释:Univer 的核心像一台电脑的主板,它本身不做具体的事,但提供插槽和总线;而工作表、公式、图表、协作这些功能就像显卡、声卡、网卡,需要哪块就插哪块。好处是你不会为了一个简单的只读表格,被迫加载一整套分析工具;坏处是你要花一点时间去理解模块依赖之间的关系,刚开始可能会踩 "缺了某个插件导致功能不生效" 的坑。
对我个人来说,理解这个结构之后,集成思路就清晰了:第一步先把核心和 Sheets 基础装上,跑起来看到空白表格;第二步再按需添加公式插件、条件格式、图表插件;第三步接入自己的数据源和事件监听。后面我会详细放这些步骤。
2.3 在线协作能力的底层逻辑
"univer在线" 这个词背后,很多人最关心的其实就是多人协作。Univer 在设计上对协作支持是友好的,但它没有像某些商业产品那样提供一个 "一键开启" 的傻瓜式开关,而是把数据同步层留给你去实现。它内部定义了完整的操作命令和数据变更结构,每个操作比如 "修改单元格 A1 的值为 100" 会变成一个可序列化的命令对象,你要做的就是把命令通过 WebSocket 发给其他客户端,再在另一端应用同一条命令。
这种思路很像协同编辑里的 "操作转换" 思想。不同客户端同时产生的操作,在后端或者接收端要经过处理,决定是按时间顺序直接应用还是做冲突调整。Univer 的数据模型支持对单个 cell 的原子性写操作,这让我接协作的时候省了不少事。我可以监听本地的 command 执行记录,实时推送到协作服务端;其他客户端收到之后,通过 API 把变更应用到自己的数据模型里,渲染层会自动响应更新。
这意味着,Univer 做的事情是:给出了协同编辑下 "文档状态怎么表达、操作怎么描述" 的标准底座。剩下网络传输、冲突仲裁、状态合并这些方案,你可以用最简单的 "后写覆盖" 或者专业的 OT/CRDT 算法来实现。门槛比从零写低很多。
3. 从零集成 Univer:四个最关键的步骤
3.1 初始化项目与安装依赖
我这边用的是 Vite + Vue 3 技术栈,但 Univer 本身是框架无关的,你完全可以放在 React 项目里,或者干脆用纯 TypeScript 接入。安装过程不复杂,核心是装不带框架后缀的 npm 包。
npm install @univerjs/presets这是目前比较推荐的快速接入方式。它把内核引擎、Sheets 渲染、基础 UI、快捷键等通用能力打成了一个大包,简单项目装这一个就够了。如果你需要公式、图表等功能,再装对应的预设包:
npm install @univerjs/presets-sheets npm install @univerjs/presets-sheets-ui npm install @univerjs/presets-formula npm install @univerjs/presets-chart为什么要分成这么多包?核心原因是按需加载。举个例子,如果你的场景是让用户只读地查看报表,不需要编辑和公式,那你完全可以只装第一个基础包,最终打包体积会明显缩小。如果不需要 UI 界面,你要自己写渲染逻辑,那么 UI 预设也可以不装。
装完之后,需要在入口文件里引入对应的 CSS。没引的话,页面会出现排版错乱的情况,我当时就漏了样式文件,导致表格区域顶到了页面最左边,工具栏的高度也是乱的。常见解决方案是直接引入已经打包好的主题样式:
import '@univerjs/presets/lib/styles.css';这一步看起来不起眼,但忽略它往往是 "白屏但控制台没报错" 的最常见原因之一。
3.2 创建 Univer 实例并挂载
依赖装好之后,创建一个 Univer 实例非常直接。你只需要准备一个容器 DOM,以及一个初始化配置对象。我会把完整的最小示例贴在这里,你可以直接复制到项目里跑。
import { Univer, createUniverSheetToolbar } from '@univerjs/presets'; import { createUniverFormula } from '@univerjs/presets-formula'; import { createUniverChart } from '@univerjs/presets-chart'; import { createUniverSheetsUIPlugin } from '@univerjs/presets-sheets-ui'; const univer = new Univer({ container: document.getElementById('app'), plugins: [ createUniverSheetsUIPlugin(), createUniverFormula(), createUniverChart(), createUniverSheetToolbar() ] }); univer.createSheet({ name: '销售数据', rowCount: 200, columnCount: 30, data: [] });这里有几个细节需要特别注意。
第一,container对应一个已经存在且有高度的 DOM 节点。如果这个节点的高度为 0,Univer 会静默失败,你连报错都看不到,页面就是白了一块。我建议在容器上写死一个 min-height,比如 500px。
第二,插件顺序有讲究。基础 UI 插件最好放在列表最前面,因为它负责初始化窗体和命令注册。公式插件如果放在后面也不会报错,但某些工具栏按钮的启用状态可能不对。
第三,创建完实例后要调用createSheet来生成一个工作簿。如果你不创建,页面上只会有一个空壳工具栏,没有表格主体。参数里的rowCount和columnCount不是固定值,它们定义的是新建工作簿的初始大小。你不必一开始就设为非常大,因为 Univer 支持动态扩容,用户输入到最后一行时,会自动扩展行数。
3.3 配置插件:公式、图表、条件格式
在 Univer 的世界里,能力分层非常明显。只装基础包,你可以在表格里打字、改样式、合并单元格;但如果想输入=SUM(A1:A10)并得到计算结果,必须装公式插件。这不只是 "计算函数库" 的有无问题,而是整个公式引擎是否参与工作簿的命令流转。
我实际遇到的情况是:装完基础 UI 后,我试着往单元格里输入公式,结果发现单元格显示的是纯文本=SUM(A1:A10),而不是计算值。查了一圈才意识到公式插件没引入。这一点非常重要,很多从 Excel 迁移过来的用户会第一时间踩中,以为表格引擎不支持公式,其实是缺了模块。
公式插件在使用时还要注意一个点:公式只能在createSheet之后写入对应的单元格。你的初始化数据如果没有包含公式,也可以通过 API 后续写入。最直接的方式是先取得当前工作簿的 commands 实例,然后执行命令:
const workbook = univer.getActiveWorkbook(); const sheet = workbook.getActiveSheet(); sheet.getRange(0, 0, 1, 1).setFormula('=SUM(B1:B10)');如果你看到公式显示正常而且计算出结果了,说明公式插件加载成功。如果显示文本,说明引擎没有执行公式解析,回去检查插件的加载顺序和版本一致性。
条件格式和图表插件的加入方式类似。条件格式的配置项比较多,包括规则类型、优先级、单元格取值范围。有一个很常见的误区:条件格式的规则,不是直接在createSheet的配置里写的,而是要通过数据模型层的 API 去添加。具体写法,Univer 提供了一套基于命令的 DSL 风格接口,建议直接参考官方示例代码。图表插件则是把数据区域和图表类型绑定,然后挂载到当前工作簿的浮动物体列表里。
3.4 接入真实业务数据
表格引擎本身是个空容器,它厉害的前提是能和你的业务数据打通。Univer 对数据接入方式是:通过单元格的 JSON 数据源直接初始化,或者通过 Range API 写入。
我用的方式是按单元格行列索引写入。因为 Univer 的行列是从 0 开始的,所以从服务端拿回来的数组数据稍作转换就能直接映射。下面是一个简单的批量写入例子:
const rows = [ { name: '华东', q1: 1200, q2: 1500 }, { name: '华南', q1: 800, q2: 1100 } ]; const range = sheet.getRange(0, 0, rows.length, 3); const data = rows.map(row => [row.name, row.q1, row.q2]); range.setValues(data);这里我把rows.length作为行数,3 作为列数,一次性写入。如果你的表头也要一起写入,注意把行数增加 1。
在数据量比较大的情况下,我不建议逐格调用setValue,性能损耗非常明显。百万行数据场景下,你更不应该直接把所有数据灌进 Univer 的渲染区,因为它本质上是给用户看的可视化编辑器,不是数据仓库。正确做法是让后端做分页、过滤、聚合,前端展示当前视图范围内的数据集,通过 Univer 只保留可视区域的数据引用。
我在项目里就是这么干的:表格区域只展示用户当前筛选出的 200~500 行,超过以后通过底部加载更多来回调后端接口,再追加到工作表末尾。这样既保证了编辑体验,又不至于拖垮页面前端。
4. 核心功能实战:公式批量计算与数据联动
4.1 公式引擎的调用方式
公式是 Univer 最吸引我的一项能力,因为它不是简单地在客户端凑几个 JS 函数,而是实现了一个完整的公式解析执行引擎。这个引擎支持单元格引用、相对引用、数组公式、跨工作表引用,还能识别工作簿名称。
在自己项目里,公式最佳实践是把公式作为数据逻辑的一部分,直接写进工作表模板。举个例子,我做了一个财务对账页面,用户会在表格里录入原始数据,然后我希望自动算出总额和差异。实现方式很简单:在表格初始化的时候,就把末行单元格预置为公式:
const sumRowIndex = sheet.getLastRow() + 1; sheet.getRange(sumRowIndex, 0, 1, 1).setValue('合计'); sheet.getRange(sumRowIndex, 1, 1, 1).setFormula(`=SUM(B2:B${sumRowIndex - 1})`);注意这里的getRange参数是用行列索引,不是 A1 坐标。很多从 Excel 宏转过来的人会在这里犯迷糊:Univer API 里没有Range("A1")这样的直接引用,你得自己完成坐标映射。如果嫌麻烦,可以用一个工具函数把 A1 文本解析成行列索引,网上有不少现成实现。
4.2 监听单元格变化并回写业务系统
在实际业务场景中,用户改了表格内容,我们需要把改动同步到后端数据库。Univer 的事件模型很丰富,我最常用的是onRangeChange事件。当某个范围内的单元格值发生变化时,事件回调里会携带新版数据和旧版数据。
sheet.onRangeChange((command) => { const { row, column, value } = command; // 构造变更请求,发送到后端 api.updateCell(row, column, value); });这里有一个很大的坑:事件回调里千万别直接调用 Univer 的setValue来回写,否则会形成死循环。正确做法是把 "用户编辑行为产生的命令" 和 "程序内部产生的命令" 区别对待,可以给程序写入的命令加一个自定义标识,然后在监听器里判断是不是来自程序自身,如果是就跳过回写。
另一个值得注意的点是,不要每次按键都触发一次后端请求。用户输入一个长文本时,会产生一系列 keydown/input 事件,最终在失焦时才有一次完整的 commit。而onRangeChange事件更像是一个操作完成后的聚合通知,不是每个按键都触发。即便如此,我还是建议在业务侧做一次节流或防抖,尤其是多人同时编辑时,请求频率会成倍上升,不节制的话后端压力很大。
4.3 通过 Command 实现撤销重做
在线表格最大的本事就是 "做错了能退回去"。Univer 的撤销重做机制建立在 Command 系统上,每个用户操作都会包装成一条命令进入历史栈。默认情况下,编辑单元格、拖拽填充、删除行列、合并单元格都是可撤销的。我需要补充的是,如果你用 API 直接修改数据模型(比如setValue),它默认不会进入撤销栈。用户按 Ctrl+Z 时,你的程序修改不会被撤销。
这个点特别容易出错。我在集成数据导入功能时就遇到过:用户从后端导入了一批数据到 Excel 表里,然后按撤销键,发现数据没有消失,以为系统坏了。实际上是因为导入数据是通过数据模型 API 直接写入的,不产生命令。
如果想要程序写入也支持撤销,必须在 Command 管理器上注册并执行一条自定义命令,而不是直接操作 sheet。我自己封装了一个简单的导入命令:
univer.getCommandRegistry().registerCommand({ id: 'import-data', type: 1, handler: async (ctx) => { // 在这里执行批量写入逻辑 } });这样用户执行导入后,通过 Command 系统发起的变更就会自动被记录,按撤销键也能正常退回导入前的状态。这算是 Univer 二次开发里比较进阶但其实很常用的知识点。
5. 协作能力深度剖析(univer 在线场景)
5.1 WebSocket 与协同编辑的流程
为什么 "univer在线" 总跟协作绑在一起?因为在线表格本质上是多人同时在一个文件里编辑。Univer 的协作方案,我建议你用 "中心化服务 + WebSocket 广播" 的方式来搭。
基本流程是这样:每个客户端在用户操作后,不是直接写进自己的状态就完了,而是通过 Univer 的 Command 监听器捕获操作命令,然后把命令序列化成 JSON 发送到 WebSocket 服务端。服务端把这个命令广播给其他连接的用户。其他客户端收到后,再把命令应用到自己的 Univer 实例里。这样所有客户端最终保持一致的状态。
实现时,关键要处理两个问题:操作时序和自身消息回环。操作时序方面,如果用户 A 在 10:00:00 改了 A1,用户 B 在 10:00:00 改了 B1,服务端要定义一条绝对顺序。最简单的方案是服务端给每条命令加一个单调递增的序列号,客户端严格按照序列号顺序应用到自己的实例上。自身消息回环方面,客户端 A 会收到自己发出的广播,你要在监听层判断消息来源,如果是自己发的,就不再应用一遍,否则状态会重复变更。
5.2 操作同步的数据结构设计
Univer 本身给了很好的数据模型底座,但协作同步时我要推荐一种更细的操作粒度设计。我不会把整个 sheet 的数据发给服务端,而是每次只发送发生变化的那些单元格。
比如用户改了 C3 的值为 "100",我会构造这样一个消息:
{ type: 'cellUpdated', sheetId: 'sheet-001', cell: { row: 2, column: 2, value: '100' }, userId: 'user-001', seq: 1234 }然后在接收端:
const sheet = workbook.getSheetById('sheet-001'); sheet.getRange(2, 2, 1, 1).setValue('100');这个方式既能保证数据实时同步,又不会产生过大的网络流量。对于公式、合并单元格、条件格式这类资源型变更,我建议单独定义消息类型,不要和普通单元格混在一起,因为它们的 AWS 生命周期、冲突处理逻辑不一样。
如果你的协作用户量很大,可能还需要在后端加一层 "版本管理"。每个用户在连接时,默认会拉取当前工作簿的全量快照;后续只接收增量消息。这样连接成本远低于每次加载都在前端重新渲染一遍。
5.3 实际部署中的连接管理
我实际部署过程中,遇到过在线状态管理的问题。当一个用户正在编辑时,如果突然断网,他的编辑内容可能会丢失。Univer 本身不会自动帮你做草稿暂存,你得在业务层实现。
一个实用方案是:在客户端监听 WebSocket 的连接状态。断线时,把用户最近 2 秒内的编辑命令缓存到一个本地队列里;重连成功后,将这个队列里的所有命令重放到服务端。这个方案能恢复大多数短时网络抖动造成的丢失,但不能覆盖长时间离线场景。更稳妥的做法是定期将整个工作簿数据存一份到 IndexedDB 中,作为本地备份。
还有一个权限问题。多人编辑同一张表时,不是所有人都应该有写权限。我建议在协作服务端维护一个 "当前可写用户集合" 的概念。服务端在广播写入命令前,检查请求来源用户的 token 是否在集合内。如果是只读用户,直接拒绝并返回错误码。Univer 前端侧可以配合设置工作表保护或者禁用编辑,但后端校验永远是必须的。
6. 常见问题与排查实录
6.1 表格渲染空白怎么办
这个是我被问得最多的一个问题。大部分情况都在于容器尺寸和依赖引用。如果你在页面初始化时,容器还处于隐藏状态(比如 tabs 切换里的非 active 面板),Univer 计算初始化尺寸时会拿到 0,于是渲染区域被设置成 0 高 0 宽,表格自然不显示。
解决方式很简单:在容器显示后再创建 Univer 实例,或者调用实例的refreshSize方法。也可以在样式上给容器设置一个固定高度,不要依赖父容器的自动高度。
另外,检查你是否引入了 CSS。我前面提过,@univerjs/presets的样式文件必须显式引入。有些版本的包还要求引入额外的图标字体文件,如果漏了,控制台可能没有任何报错,但工具栏图标会变成方框或者空白,看起来像 "白了半边"。
6.2 公式不执行的典型原因
公式不执行,首先分清两种情况:输入公式后单元格显示原始文本,还是显示#NAME?这样的错误。
如果是原始文本,大概率是公式插件没安装或没有注册。你如果是通过createUniverFormula()初始化的,检查函数名写没写错,以及包版本是否和@univerjs/presets一致。版本不一致时,插件在注册过程中可能被静默跳过。
如果是#NAME?,说明函数不存在或写错了函数名。Univer 支持的函数列表在官方文档有,注意区分逗号和分号,中文输入法下的逗号是分隔符问题最常见的来源。还有一个隐蔽的坑:公式里的单元格引用如果带上了工作簿名或工作表名,格式必须是Sheet1!A1,如果你的工作表名包含空格,还需要用单引号包起来,像'销售数据'!A1,否则解析会失败。
6.3 大数据量卡顿的性能优化
说实话,Univer 的渲染性能已经很好,但也架不住往里面塞 10 万行。我压测的时候发现,行数超过一万之后,滚动略有延迟,尤其是同时设置了多行条件格式时,卡顿会比较明显。
第一,不要一次加载大数据。这是最有效的优化。第二,把计算密集的操作放到异步任务里,避免阻塞 UI 渲染。第三,如果有大量公式,可以把公式改为 "手动重算" 模式,让用户在确认修改后手动触发一次全量重算,而不是每次编辑都自动重算。Univer 提供了命令注册或配置参数来控制这个行为,具体可以看看初始化的的workbook配置项。
另外,渲染锁也是个大坑:你在大量写入数据时,如果不暂停 UI 刷新,用户会看到表格区域被一帧一帧地重画,非常卡。我建议在批量导入数据时,先将工作簿的渲染暂停,等数据全部写入后再刷新视图。Univer 有一种Command之外的 API 是能做 "原子性批量操作" 的,我在实践中用的就是先调用suspendRendering类似的内部服务,再写入,最后恢复渲染。
6.4 和其他前端框架集成注意事项
Univer 是框架无关的,但你用 React 或 Vue 时,要注意生命周期管理。特别是组件卸载时,必须销毁 Univer 实例,否则会留下事件监听和定时器,造成内存泄漏。
在 React 里你可以这样做:
useEffect(() => { const univer = new Univer({ container, ... }); return () => univer.dispose(); }, []);在 Vue 里对应的是onBeforeUnmount。如果你用了路由切换,不销毁实例的话,切回来时页面大概率会报 "容器已被占用" 的错,因为同一个 DOM 节点被重复初始化了。即使每次使用不同的节点,旧实例占用的资源也不会自动释放,长时间运行会越来越卡。
另外,Univer 实例和框架的状态管理最好分离。不要在 Vuex/Pinia 的 store 里直接塞一个 Univer 实例,这会让它变成不可序列化对象,导致状态管理工具报警告。正确做法是把 Univer 实例保存在一个非响应式的普通对象里,只在需要操作的组件中去获取。
最后再分享点实际体会
集成 Univer 的这半个月,我最大的感受是:它的技术底子非常扎实,官方在架构设计上确实下了功夫,不是那种 demo 级的开源项目。但你要接受一个现实:它的文档和社区沉淀,相比那些火了很多年的开源项目还是有一定差距,一些高级用法需要自己翻源码、跑 demo 才能搞清楚。
所以我建议所有准备上这个框架的朋友,先在本地跑通官方的示例项目再去接自己业务。遇到问题时,优先看 GitHub Issues 和源码,网上现成博客不多。而且由于版本迭代很快,你搜到的旧教程很可能已经失效,直接看代码里的类型定义往往更可靠。
如果用一句话来总结,Univer 给开发者提供的不是 "一个表格插件",而是 "一套可以长期演进、深度定制的在线表格内核"。在这个基础上,你能构建出来的想象空间很大。希望我这篇实战记录,能帮你少走几步弯路。