Univer 表格 SDK 集成实战:Canvas 渲染与插件架构解析
电子表格这个东西,大家天天在用,但真要自己动手做一个,或者把表格能力嵌到自己的产品里,很多人第一反应是"这活儿太重了"。确实,传统方案要么是直接集成一个完整的办公套件,要么是从零手撸一套渲染和计算引擎,前者太重、定制性差,后者门槛高、周期长。而 univer 这个项目,恰好卡在了一个很舒服的位置上——它是一套开源的、可嵌入的表格与文档 SDK,用 Canvas 做渲染,用插件架构做能力拆分,跑在 Node.js 生态里,既能当独立应用用,也能作为前端 SDK 塞进你自己的系统。
我接触 univer 的契机,是团队要在一个内部数据平台里加一块"在线表格"能力,要求支持公式、多 sheet、协同编辑的扩展空间,同时不能引入一个几 MB 起步的庞然大物。试过几个方案之后,univer 的插件化设计和 Canvas 渲染路线让我觉得值得深挖。这篇就把我从环境搭建、核心概念理解、到实际集成和踩坑的完整过程梳理出来,适合两类人看:一类是想快速把 univer 跑起来看看效果的开发者,另一类是想评估它能不能扛起自己业务场景的技术负责人。文中涉及 Node.js、Canvas、插件架构、SDK 集成这些关键词,都会结合实际操作讲清楚。
1. 先把 univer 是什么这件事说透
1.1 它不是"另一个在线 Excel",而是一套可组装的表格内核
很多人第一次看到 univer,会下意识把它归类成"在线表格工具",然后拿去和市面上的在线文档产品对比。这个理解方向其实偏了。univer 的定位更接近"表格能力的底层引擎 + 可插拔的功能模块",它把电子表格拆成了渲染层、数据层、公式层、协同层等一堆独立的能力单元,每个单元以插件形式存在,你按需引入。
这个设计思路带来的直接好处是:你不需要为了用一个单元格编辑功能,就把整个办公套件搬进来。比如你只想做一个"数据填报表格",那公式引擎、协同模块这些完全可以不引入,包体积和复杂度都能压下来。反过来,如果你要做的是一个功能完整的在线表格产品,那把这些插件全装上,它也能撑得住。
从技术栈上看,univer 的核心渲染走的是 Canvas 路线,而不是 DOM 表格。这一点很关键,后面会专门讲为什么。整个项目基于 TypeScript 开发,运行环境覆盖浏览器和 Node.js,所以它既能做前端 SDK,也能在服务端做表格数据的处理,比如批量导出、公式计算、格式转换这类活儿。
1.2 为什么是 Canvas 而不是 DOM
这是我在选型时问自己的第一个问题。DOM 表格(就是一堆 table、tr、td 拼出来的那种)在数据量小的时候完全够用,浏览器原生支持,调试也方便。但一旦行数上万、列数上百,DOM 节点数量会爆炸,滚动和编辑都会卡到没法用。这是 DOM 方案的天然瓶颈——每个单元格都是一个真实节点,浏览器要维护它们的布局、样式、事件,开销随数据量线性甚至超线性增长。
Canvas 方案则是把整个表格画在一张画布上,单元格不是真实节点,而是绘制出来的像素。这样一来,无论表格有多少行多少列,DOM 层面始终只有那么几个 canvas 元素,渲染开销和节点数量脱钩。滚动的时候只需要重绘可视区域,性能表现稳定得多。univer 正是吃到了这个红利,才能在大数据量场景下保持流畅。
当然 Canvas 也有代价。最直接的就是"什么都得自己实现"——文本选中、光标定位、输入法处理、无障碍支持,这些在 DOM 里浏览器帮你搞定的东西,Canvas 方案全得手写。univer 在这方面做了大量工作,比如自己实现了文本测量、选区绘制、输入代理层(用一个隐藏的 DOM 元素承接键盘和输入法事件,再把内容同步到 Canvas 上)。这也是为什么它的代码量不小,因为渲染引擎本身就是个重活。
提示:如果你的表格数据量长期在几百行以内,其实 DOM 方案更省心。Canvas 方案的价值在数据量大、需要精细控制渲染的场景才体现得出来。选型时别为了"技术先进"而先进。
1.3 插件架构到底解决了什么问题
univer 的插件架构是我最欣赏的部分。它把功能拆成了一个个 plugin,每个 plugin 负责一块能力,通过依赖注入和生命周期钩子串联起来。举个具体的例子,公式计算这个能力,在 univer 里是由公式插件提供的,它依赖数据层插件拿到单元格的值,依赖渲染插件把计算结果画出来。这些依赖关系是声明式的,框架负责组装。
这种设计对使用者的意义在于"可裁剪"和"可扩展"。可裁剪是说,你按需引入插件,不要的功能不引入,包就小。可扩展是说,你想加一个新功能,比如自定义一种单元格类型,写个插件注册进去就行,不用改核心代码。
我实际用下来,插件架构最大的价值其实在"隔离"。表格系统很容易变成一坨互相纠缠的代码——渲染逻辑里混着数据逻辑,数据逻辑里又调用了公式计算。插件化强制把这些关注点分开,每个插件只干自己的事,通过明确定义的接口通信。这在后期维护和多人协作时优势非常明显。
2. 环境搭建:从 Node.js 到第一个可运行实例
2.1 Node.js 版本选择和安装的那些细节
univer 的开发环境依赖 Node.js,这一步看着简单,但版本选不对后面会踩坑。根据我的实测,建议用 Node.js 18 LTS 或更高的 LTS 版本,比如 18.20.4 这种长期支持版,或者 20.x、22.x 的 LTS。太老的版本(比如 14、16)可能在依赖安装阶段就报错,因为一些现代构建工具要求更高的 Node 版本。
安装方式上,Windows 和 macOS 用户直接去官网下 LTS 安装包最省事,一路下一步就行。Linux 用户(比如 CentOS 7.9 这类环境)建议用 nvm 或者 NodeSource 的源来装,因为系统自带的 Node 版本往往太老。装完之后验证一下:
node -v npm -v两条命令都能正常输出版本号,说明装好了。如果提示 command not found,多半是环境变量没配好,检查一下安装路径有没有加到 PATH 里。
这里有个容易被忽略的点:如果你机器上装了多个 Node 版本,一定要确认当前用的是哪个。我见过有人node -v显示 18,但 npm 全局包却装在 16 的环境里,导致各种诡异的模块找不到。用 nvm 的话,nvm current能告诉你当前版本,切换用nvm use 18。
注意:不要用
sudo npm install -g去装全局包,尤其是在 macOS 和 Linux 上。这会把包装到系统目录,权限混乱,后面升级 Node 版本时全局包全丢。正确做法是配好 npm 的全局目录,或者干脆用 nvm 管理。
2.2 创建项目并引入 univer 依赖
环境好了之后,建一个空项目。我习惯用 Vite 起一个 TypeScript 模板,因为 univer 本身是 TS 写的,类型提示能省很多事:
npm create vite@latest my-univer-demo -- --template vanilla-ts cd my-univer-demo npm install然后装 univer 的核心包。univer 是拆成多个 npm 包发布的,核心包加上预设包,一般这样引入:
npm install @univerjs/core @univerjs/design @univerjs/engine-formula @univerjs/engine-render @univerjs/sheets @univerjs/sheets-ui @univerjs/ui这里解释一下这几个包各自管什么。@univerjs/core是内核,提供插件系统、依赖注入、生命周期这些基础设施。@univerjs/engine-render是渲染引擎,Canvas 那套东西在这里。@univerjs/engine-formula是公式引擎。@univerjs/sheets是表格的数据模型,@univerjs/sheets-ui是表格的界面交互,@univerjs/ui是通用 UI 组件。@univerjs/design是设计系统,提供主题和基础样式。
装的时候留意一下版本号,univer 迭代比较快,不同包之间版本要对齐,不然容易出现 API 不匹配。建议在 package.json 里锁定版本,或者用同一个大版本号。
2.3 最小可运行实例的完整代码
依赖装好,写一个最小的表格实例。核心逻辑是:创建一个 Univer 实例,注册需要的插件,然后把它挂载到页面的一个容器上。
import { Univer, LocaleType, merge } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import { defaultTheme } from '@univerjs/design'; // 中文语言包,按需引入 import DesignZhCN from '@univerjs/design/locale/zh-CN'; import UIZhCN from '@univerjs/ui/locale/zh-CN'; import SheetsZhCN from '@univerjs/sheets/locale/zh-CN'; import SheetsUIZhCN from '@univerjs/sheets-ui/locale/zh-CN'; const univer = new Univer({ theme: defaultTheme, locale: LocaleType.ZH_CN, locales: { [LocaleType.ZH_CN]: merge( {}, DesignZhCN, UIZhCN, SheetsZhCN, SheetsUIZhCN ), }, }); // 注册插件,顺序有讲究,后面会讲 univer.registerPlugin(UniverRenderEnginePlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container: 'app', }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin); // 创建一张工作表 univer.createUnit(UniverInstanceType.UNIVER_SHEET, { id: 'demo-sheet', name: '示例表格', sheetOrder: ['sheet-01'], sheets: { 'sheet-01': { id: 'sheet-01', name: 'Sheet1', rowCount: 100, columnCount: 20, cellData: { 0: { 0: { v: '姓名' }, 1: { v: '年龄' }, }, 1: { 0: { v: '张三' }, 1: { v: 28 }, }, }, }, }, });HTML 那边只需要一个容器:
<!DOCTYPE html> <html> <head> <title>Univer 示例</title> <style> html, body { margin: 0; padding: 0; height: 100%; } #app { width: 100%; height: 100vh; } </style> </head> <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body> </html>跑起来npm run dev,浏览器里应该就能看到一张可编辑的表格了。如果页面空白,先看控制台报错,八成是插件注册顺序或者容器 id 对不上。
2.4 插件注册顺序为什么不能随便写
上面代码里插件注册是有顺序的,这不是随便排的。univer 的插件之间有依赖关系,被依赖的插件要先注册。渲染引擎是基础,得先有;公式引擎依赖数据模型,但数据模型插件注册时又会用到公式引擎的接口,所以这两个的顺序要按官方示例来。UI 插件要在容器准备好之后注册,因为它要往容器里挂 DOM。
我踩过一次坑:把UniverSheetsUIPlugin注册在了UniverSheetsPlugin前面,结果表格数据加载了但界面渲染不出来,控制台报"找不到 sheet 渲染器"。原因就是 UI 插件初始化时去拿数据模型,但数据模型插件还没注册,拿了个空。所以顺序这事,宁可照着官方示例抄,也别自己发挥。
3. 核心概念:把 univer 的骨架摸清楚
3.1 Univer 实例、Unit 和 Sheet 的层级关系
用 univer 之前,得先理清它的对象模型。最顶层是Univer实例,你可以理解成一个"应用容器",它管理着所有的插件和资源。一个页面通常只需要一个 Univer 实例。
Univer 实例下面挂着若干Unit,Unit 是一个抽象概念,代表"一个可编辑的文档单元"。表格是一种 Unit,文档是另一种 Unit。每个 Unit 有自己的 id 和类型。再往下,一个表格 Unit 里可以有多个Sheet(工作表),就是我们熟悉的 Sheet1、Sheet2 那种。
这个层级关系决定了你操作数据的入口。想改某个单元格的值,得先拿到对应的 Unit,再拿到 Sheet,再定位到行列。univer 提供了getUniverSheet这类方法帮你从实例往下取。
const sheet = univer.getUniverSheet('demo-sheet'); const worksheet = sheet?.getSheetBySheetId('sheet-01'); const cellValue = worksheet?.getCell(1, 0)?.v; // 拿到"张三"理解这个层级,后面做数据读写、事件监听、自定义扩展时就不会迷路。
3.2 数据模型:cellData 的结构和读写方式
univer 的单元格数据存在cellData里,结构是"行索引 -> 列索引 -> 单元格对象"的嵌套对象。注意这里的行列索引是从 0 开始的数字,不是 A1 那种坐标。单元格对象里v是值,f是公式,s是样式引用,t是类型。
// 写入一个带公式的单元格 worksheet.getRange(2, 0).setValue({ f: '=SUM(B2:B10)', }); // 读取 const range = worksheet.getRange(0, 0, 1, 2); // 起始行、起始列、行数、列数 const values = range.getValues();这里有个性能上的经验:批量读写一定要用 Range 的批量接口,别一个单元格一个单元格地循环调用。每次单独调用都会触发一次内部的状态更新和可能的渲染,循环一千次就是一千次开销,卡到怀疑人生。用getValues()和setValues()一次性处理,效率差几十倍。
提示:univer 的坐标是"行在前、列在后",和很多人习惯的"先列后行"(比如 Excel 的 A1 记法)相反。写代码时特别容易搞反,建议在变量命名上就带上 row、col 前缀,减少出错。
3.3 命令系统:为什么改数据要走 Command
univer 里修改数据不是直接改对象,而是通过"命令"(Command)来执行。比如设置单元格值,实际是派发一个SetRangeValuesCommand。这个设计初看有点绕,但背后有很实在的理由。
第一是撤销重做。命令模式天然支持 undo/redo,每个命令都有正向和反向操作,撤销时执行反向就行。如果直接改数据,撤销功能就得自己维护一套历史快照,麻烦且容易出错。
第二是协同编辑。在协同场景下,本地操作要同步给其他端,走命令的话,把命令序列化发出去就行,其他端执行同样的命令,状态自然一致。直接改数据就没法这么优雅地同步。
第三是拦截和扩展。命令在派发过程中可以被拦截、修改、增强。比如你想在用户设置值之前做一次校验,拦截命令就行,不用改核心代码。
import { SetRangeValuesCommand } from '@univerjs/sheets'; univerAPI.executeCommand(SetRangeValuesCommand.id, { unitId: 'demo-sheet', subUnitId: 'sheet-01', range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }, value: { v: '新值' }, });理解命令系统是深入使用 univer 的分水岭。只会用 API 读写数据,那停留在表面;理解了命令,才能做拦截、扩展、协同这些高级玩法。
3.4 事件机制:监听表格变化
univer 有一套事件系统,表格的各种变化都会派发事件,你可以监听这些事件做响应。比如监听单元格值变化、选区变化、sheet 切换等。
import { ICommandService } from '@univerjs/core'; // 监听命令执行 const commandService = univer.__getInjector().get(ICommandService); commandService.onCommandExecuted((command) => { if (command.id === SetRangeValuesCommand.id) { console.log('单元格值被修改了', command.params); } });事件机制在业务集成里非常有用。比如你想在用户编辑某个单元格后自动触发一次数据校验,或者把修改同步到后端,监听命令执行事件就是入口。注意别在事件回调里做太重的事,因为它是同步执行的,会阻塞后续操作。重活建议丢到异步队列里。
4. 实际集成中的关键决策和踩坑记录
4.1 包体积优化:按需引入到底能省多少
univer 全量引入的话,打包体积不算小。但它的插件架构让按需引入变得很自然。我做过一次对比,在一个只做"数据展示 + 简单编辑"的场景里,去掉公式引擎、协同相关、图表相关这些插件后,打包体积能降下来一大截。
具体做法是只注册真正用到的插件。比如你的场景不需要公式,那UniverFormulaEnginePlugin就别注册,@univerjs/engine-formula这个包也不会被打进去。同理,不需要多 sheet 切换、不需要右键菜单、不需要工具栏的,对应的插件都可以砍。
不过要注意,有些插件之间有隐式依赖。比如UniverSheetsUIPlugin可能依赖UniverUIPlugin提供的基础 UI 能力,砍的时候要顺着依赖链检查,别砍出运行时错误。我的做法是先全量跑通,然后一个一个移除插件,每移除一个跑一次,确认没问题再移下一个。虽然笨,但稳。
4.2 大数据量下的渲染性能实测
Canvas 渲染的优势在大数据量下才明显。我做过一个测试,往表格里塞 10000 行、50 列的数据,然后滚动、编辑、筛选,观察帧率。
结果是这样的:初始加载时,因为要构建数据模型和首次渲染,会有个几百毫秒的耗时,这个躲不掉。但加载完之后,滚动非常流畅,基本稳定在 60 帧。编辑单元格的响应也在几十毫秒内。相比之下,同样数据量用 DOM 表格,滚动时帧率会掉到 20 以下,明显卡顿。
这里有个优化点:univer 默认会渲染可视区域加上一定的缓冲行,如果你发现滚动时还是有轻微卡顿,可以调整缓冲行数。缓冲太少,快速滚动时会看到空白;缓冲太多,每次重绘的计算量又上去了。这个值需要根据你的实际场景调。
注意:数据量大的时候,避免在单元格里塞过于复杂的样式。每个单元格的样式都会参与渲染计算,样式越复杂,重绘越慢。能用单元格样式解决的,别用富文本。
4.3 输入法处理的坑:中文输入的那些事
Canvas 方案处理中文输入法是个老大难。因为 Canvas 上没有真实的输入框,输入法不知道往哪儿挂。univer 的做法是在 Canvas 上方叠一个透明的 DOM 输入元素,用户打字时事件先到这个 DOM 元素,再把内容同步到 Canvas 的单元格里。
这个方案大部分时候没问题,但在某些浏览器和输入法组合下会有诡异表现。我遇到过的情况包括:输入拼音时候选框位置偏移、快速输入时字符丢失、切换输入法后光标位置错乱。这些问题多半和输入代理层的定位计算有关。
实测下来,Chrome 和 Edge 上表现最稳,Safari 偶尔有候选框位置问题,Firefox 相对少见但也遇到过。如果你的产品对中文输入体验要求高,建议在目标浏览器上都实测一遍,尤其是移动端。移动端的输入法行为差异更大,iOS Safari 上还涉及键盘弹起时视口变化的问题,需要额外处理。
4.4 服务端使用:在 Node.js 里跑 univer
univer 不只能在浏览器跑,Node.js 环境也能用。这个能力在做服务端导出、批量计算时很有价值。比如用户要导出表格为 Excel,或者要在服务端批量重算公式,都可以在 Node 里跑 univer。
服务端使用和浏览器端的主要区别是渲染层。服务端没有 Canvas,所以渲染相关的插件不需要注册,你只需要数据层和公式层。这样跑起来更轻,也不依赖浏览器环境。
import { Univer } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverSheetsPlugin } from '@univerjs/sheets'; const univer = new Univer(); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverSheetsPlugin); // 加载数据、执行公式计算、导出结果服务端场景下要注意的是,公式计算是同步的,如果表格很大、公式很复杂,可能会阻塞事件循环。建议把这类操作放到 worker 线程里,或者做成分批处理,别把主线程堵死。
5. 扩展 univer:写一个自己的插件
5.1 插件的基本结构
univer 的插件就是一个类,继承自Plugin,实现生命周期方法。核心是onStarting和onReady这两个钩子,前者在插件启动时调用,后者在所有插件都就绪后调用。
import { Plugin, ICommandService } from '@univerjs/core'; export class MyCustomPlugin extends Plugin { static override type = 'my-custom-plugin'; override onStarting(): void { // 注册命令、监听事件等 } override onReady(): void { // 所有插件就绪后的初始化 } }插件里可以拿到依赖注入容器,从中取出其他服务,比如命令服务、配置服务、日志服务。这就是插件之间通信的方式——不直接引用对方,而是通过容器拿接口。
5.2 注册一个自定义命令
写插件最常见的需求是加一个自定义命令。比如你想加一个"批量填充空值"的功能,就注册一个命令,然后在 UI 上挂个按钮触发它。
import { CommandType, ICommandService } from '@univerjs/core'; export const FillEmptyCommand = { id: 'my-plugin.command.fill-empty', type: CommandType.COMMAND, handler: (accessor, params) => { const commandService = accessor.get(ICommandService); // 执行填充逻辑 return true; }, }; // 在插件的 onStarting 里注册 override onStarting(): void { const commandService = this._injector.get(ICommandService); commandService.registerCommand(FillEmptyCommand); }命令的 handler 里可以访问注入容器,拿到需要的服务。返回值表示命令是否执行成功,返回 false 的话,这个命令不会进入撤销栈。
5.3 拦截已有命令做增强
比注册新命令更实用的,是拦截已有命令做增强。比如你想在用户设置单元格值之前做一次权限校验,或者记录操作日志,都可以拦截SetRangeValuesCommand。
commandService.interceptCommand({ getMutations(command) { if (command.id === SetRangeValuesCommand.id) { // 在这里做校验、记录等 // 返回空数组表示不拦截,正常执行 return []; } return []; }, });拦截机制让 univer 的扩展性上了一个台阶。你可以在不改核心代码的前提下,往任何命令的执行流程里插入自己的逻辑。这在做企业级定制时特别有用,比如加审计日志、加数据脱敏、加业务规则校验。
5.4 自定义单元格渲染
如果内置的单元格类型满足不了需求,可以自定义渲染。比如你要做一个"进度条单元格",值是个百分比,显示成一条进度条。这需要注册一个自定义的单元格渲染器,在 Canvas 上画出来。
这部分涉及渲染引擎的底层接口,门槛相对高一些。核心是实现一个渲染器类,在draw方法里用 Canvas API 画你要的内容。univer 的渲染引擎提供了绘制上下文、坐标转换、样式继承这些基础设施,你只需要关注"画什么"。
我的建议是,除非确实需要,否则优先用内置能力组合实现。自定义渲染的维护成本不低,尤其是涉及交互(比如进度条要能拖动)的时候,事件处理、命中检测都得自己写。真要做,先从小范围试点,跑通了再推广。
6. 几个高频问题的排查思路
6.1 页面空白或表格不显示
这是最常见的问题,排查顺序建议这样走。先看控制台有没有报错,报错信息通常能直接定位问题。如果没有报错但页面空白,检查容器元素是否存在、id 是否对得上、容器有没有宽高。univer 挂载时如果容器高度是 0,表格就渲染不出来,因为 Canvas 尺寸算出来是 0。
再检查插件注册顺序,尤其是 UI 插件和数据插件的前后关系。最后确认语言包有没有正确引入,某些情况下语言包缺失会导致初始化中断。
6.2 公式不计算或计算结果不对
公式相关的问题,先确认公式引擎插件注册了没有。然后检查公式字符串的格式,univer 的公式和 Excel 类似,以等号开头,函数名大写。如果公式引用了其他单元格,确认引用的坐标对不对。
还有一个容易忽略的点:公式计算是异步的,设置完公式后不会立刻拿到结果。如果你在设置公式后马上读值,可能读到的是空。正确做法是监听计算完成事件,或者在下一个事件循环里再读。
6.3 编辑时输入框位置偏移
这个问题多半和滚动有关。univer 的输入代理层需要根据单元格在 Canvas 上的位置来定位输入框,如果滚动后位置没更新,输入框就会偏。检查一下是不是在滚动事件里正确触发了位置更新。另外,如果容器有 CSS transform 或者缩放,坐标计算会更复杂,需要额外处理。
6.4 打包后体积过大
先分析打包产物,看看哪些包占了大头。通常公式引擎和协同相关的包体积较大,如果没用到就砍掉。另外注意有没有重复引入,比如同时引了多个版本的同一个包。用打包分析工具看一眼依赖树,往往能发现意外的大块头。
7. 我对 univer 适用场景的判断
用了一段时间下来,我对 univer 的适用边界有了比较清晰的认识。它最适合的场景是"需要把表格能力嵌入自有产品,且对定制性和数据量有要求"的情况。比如数据平台里的在线表格、报表系统里的可编辑表格、低代码平台里的表格组件,这些场景下 univer 的插件架构和 Canvas 渲染都能发挥价值。
不太适合的场景是"只需要一个简单的静态表格展示"。这种需求用原生 table 或者轻量的表格库就够了,引入 univer 属于杀鸡用牛刀,包体积和复杂度都不划算。
另外,如果你的团队对 Canvas 渲染、命令模式这些概念不熟,上手 univer 会有一定学习成本。它的 API 设计是给"要深入定制"的人准备的,不是那种开箱即用、几行代码搞定的库。评估时要考虑团队的学习曲线。
从生态角度看,univer 还在快速迭代,版本之间偶尔有 breaking change。用的时候建议锁定版本,升级前先看 changelog,别盲目追新。社区活跃度还不错,遇到问题去 issue 区搜一搜,大概率有人踩过同样的坑。
最后分享一个我自己的使用习惯:在正式集成到产品之前,先花半天时间把官方示例完整跑一遍,把每个插件的效果都看一遍。这样你对"哪些插件提供什么能力"会有直观认识,后面做取舍时心里有数。光看文档容易漏掉细节,动手跑一遍印象深得多。