
这几年聊在线表格绕不开两个名字一个是 Excel另一个就是 Luckysheet 的后继者 Univer。我最早接触 Univer是在一次给客户做在线数据填报项目的选型会上。团队要在 Vue 管理后台里嵌入一个类 Excel 的编辑能力试过纯前端表格方案、商业表格控件最后在开源社区看到了 Univer 这个项目才意识到它不是一个普通表格组件而是一整套可扩展的在线办公基础设施表格、文档、幻灯片的能力都被抽象成了可以按需集成的 SDK。这篇文章不打算复述官方文档而是从一个实际做过集成、踩过坑的开发者视角出发把 Univer 是什么、为什么值得选、最小集成怎么做、二次开发从哪里入手、实测有哪些坑一次性讲清楚。适合正在做在线表格能力选型的前端团队也适合对现代 Web Office 技术栈感兴趣、想了解这套东西内部怎么运转的开发者。1. 先搞清楚 Univer 是什么从 Luckysheet 到办公套件 SDK聊 Univer 之前必须提 Luckysheet。它曾经是国内开源社区里最火的纯前端表格项目界面像 Excel源码开放很多团队直接拿来嵌进后台系统。但真正要往企业级用的时候问题全暴露出来了表格业务逻辑和界面渲染耦合在同一个项目里想新增一个公式要动核心代码数据量到几十万行时基于 DOM 的滚动和绘制开始明显卡顿文档类型单一做不了 Word、PPT 这类能力协同更是要从零开始搭。开发团队自己也意识到继续在 Luckysheet 上打补丁已经没有出路于是重新设计了整套内核这就是如今的 Univer。1.1 Univer 的定位不是表格组件而是办公基础设施用一句话概括Univer 是一个跨前端框架、可扩展的办公套件 SDK目前最成熟的是电子表格Sheet文档Doc和幻灯片Slide也在逐步完善。它把 Excel 级别的编辑能力抽象成一套基础设施你不需要关心单元格怎么绘制、公式怎么解析、撤销重做怎么实现只需要在自己的应用里注册插件、挂载 UI、接入数据。这一点和市面上的表格组件有本质区别。普通表格组件给你的是“一个能编辑的表格”Univer 给你的是“一个能长出表格/文档/幻灯片的能力基座”。正因为它做的是能力基座所以它的集成方式完全模块化你不想要工具栏就不加载工具栏插件你只需要公式计算不需要界面可以单独把公式引擎丢到 Web Worker 里跑。这种设计对后端数据密集型任务非常友好。1.2 真实成熟度别被宣传带偏客观讲一下 Univer 现在的成熟度因为选型最怕预期错位。表格能力成熟度最高。单元格编辑、样式、公式、筛选、复制粘贴、撤销重做都有了日常业务里的表格功能基本覆盖。文档和幻灯片还处于相对早期阶段有可用的 demo 和框架但离生产级有距离。如果你的核心诉求是 Word/PPT 编辑现阶段不要押注在 Univer 上。协同编辑前端的数据结构和命令机制已经为协同设计好了但实际多人协作需要自己搭建服务端官方并不提供一个开箱即用的协同后端。版本稳定性Univer 目前还在 0.x 阶段API 变动比较频繁。这意味着你要有一个心理准备升级版本可能不是“换个版本号”那么简单得跟着 breaking change 改代码。我在团队里第一次做评估时把文档看了一遍又去官方 Playground 实际拖拽操作了几分钟才敢跟项目经理说“这个方案可以试”。如果你也打算引入 Univer建议先在自己的业务场景里做个小 Demo别只凭主页上的效果图做决定。2. 拆开看架构插件化内核、渲染引擎、命令系统与公式引擎在接入 Univer 之前理解它的架构会让你少走很多弯路。这套设计不是炫技而是所有扩展点、性能特性和协同能力的地基。2.1 插件化内核与领域分层的意义Univer 的 npm 包非常多按职责可以分成几层内核层univerjs/core负责定义文档数据模型、Univer 实例、插件生命周期和命令机制。引擎层univerjs/engine-render渲染引擎、univerjs/engine-formula公式引擎。业务层univerjs/sheets、univerjs/sheets-ui表格业务和表格界面 UI。能力扩展univerjs/import-xlsx、univerjs/export-xlsxExcel 文件导入导出等。这个分层的核心价值在于“按需加载”和“职责隔离”。你不做文档业务就不用引入 Doc 插件你只想做公式计算不想要界面可以直接在纯 Node 环境里跑公式引擎。这种架构在企业级应用里非常实用因为很多后台系统对包体积和初始化性能有硬指标按需加载能让首屏保持在可控范围。插件化的另一层含义是二次开发的标准化。你想加一个自定义功能不需要去改 Univer 源码而是写一个插件注册进去插件的生命周期由内核统一管理可以监听文档变化、注册工具栏按钮、自定义右键菜单。这类开发模式一旦跑通后续功能扩展就是一个接一个注册插件的事。2.2 渲染引擎Canvas 自绘与窗口化绘制Univer 没有用 DOM 表格来渲染单元格而是基于 Canvas 自绘。这一点是它能支撑大数据量和流畅滚动的前提。如果沿用 DOM 表格成千上万个单元格节点会直接拖垮浏览器而 Canvas 的绘制模式是“只画看得见的部分”。具体来说Univer 的渲染引擎做了窗口化处理根据当前视口位置和滚动 offset只计算可视区域内的单元格然后重新绘制。滚动时它不会移动 DOM 节点而是重新计算绘制范围。配合分层渲染——背景层、文本层、边框层、选区层分开绘制——可以做到复杂画面前提下依然流畅。这里补充一个容易忽略的点因为走 Canvas 自绘前端那些基于 DOM 的表格截图工具比如直接抓 DOM 结构生成图片对 Univer 是不适用的。你要么通过它的渲染引擎接口导出画布内容要么在测试截图时换一种思路。这也是我后面要单独说的坑之一。2.3 命令系统所有操作都是可撤销、可广播的Univer 的另一个核心设计是命令系统。用户在界面上做的每一次操作不管是输入一个字符、合并单元格还是删除一行都会封装成一个 Command。命令的执行路径是固定的用户操作 - 派发命令 - 命令校验 - 修改数据模型 - 触发监听 - UI 重绘。这个设计带来两个直接好处。第一撤销和重做变得非常简单因为历史记录里存的就是一串命令撤销就是逆序回放命令第二协同变得可期因为每个操作都有明确的、可序列化的描述可以把命令广播给远端用户远端执行同一条命令就能达到一致状态。这也是为什么 Univer 一直在强调它的架构“原生支持协同”因为数据流层面已经为同步做好了准备。我在看代码时发现命令系统还有个隐藏优势它让自动化测试变得容易。你可以不通过 UI直接派发命令然后断言数据模型的变化。这种可测试性对团队内部的质量保障很重要。2.4 公式引擎独立于 UI 的计算内核公式引擎是 Univer 架构里比较特别的一部分。它被拆成了一个独立的引擎包不依赖渲染层和 UI 层可以单独在 Web Worker 或者 Node 环境中运行。这意味着如果你有一个批量计算任务不必等界面加载完直接把公式引擎丢到后台线程计算。公式引擎支持 Excel 常用函数的绝大部分也支持自定义函数注册。它的计算结果是数据模型的一部分数据模型变化后再通知 UI 层重绘因此“计算”和“显示”是解耦的。在实际业务里这种解耦非常关键——大数据量填报时界面不会因为公式重算而被阻塞到卡死。3. 半小时跑通最小集成从空目录到一张可编辑表格纸上谈兵到这里为止下面进入实操。我以 Vite TypeScript 为例带你从空目录跑到一张可编辑表格。Univer 官方文档里有更完整的示例我这里给的是我实际试过能跑通的最小骨架。3.1 环境准备与版本注意先确认 Node 版本。建议 Node 18 以上干净一点的 20 LTS 更稳定。然后初始化项目npm create vitelatest univer-demo -- --template vanilla-ts cd univer-demo npm install安装 Univer 相关依赖。这里强调一次下面这些包的版本必须保持一致不然很容易出现插件注册后没反应、样式错乱的问题。npm install univerjs/core univerjs/design univerjs/ui univerjs/sheets univerjs/sheets-ui univerjs/engine-render univerjs/engine-formula3.2 初始化最小 Univer 实例打开src/main.ts写入以下内容import { Univer, LocaleType } from univerjs/core; import { defaultTheme } from univerjs/design; import { UniverUIPlugin } from univerjs/ui; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; import univerjs/design/lib/index.css; import univerjs/ui/lib/index.css; const univer new Univer({ locale: LocaleType.ZH_CN, theme: defaultTheme, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsUIPlugin); univer.registerPlugin(UniverUIPlugin, { container: #app, });这段代码做的事很清楚创建 Univer 内核实例注册表格业务插件、表格 UI 插件和基础 UI 插件把整个编辑器挂载到#app节点上。提示Univer 在 0.x 阶段迭代非常快不同小版本的插件注册方式和初始化参数可能略有差异。如果你安装的版本里某个类名找不到去node_modules里翻对应的类型声明或者直接对照官方 Playground 里的最新示例调整。这不是文档偷懒而是这类仍在快速演进的项目普遍存在的现实。3.3 创建工作表并填入数据只挂载 UI 还不够你要主动创建一个工作表Univer 才会渲染出内容。不同版本 API 名有变化早期版本是createUniverSheet我当前实测的是这种写法const univerSheet univer.createUniverSheet({ name: 示例工作表, sheets: { sheet-01: { name: Sheet1, cellData: { 0: { 0: { v: Hello }, 1: { v: Univer }, }, 1: { 0: { v: 2024 }, 1: { v: 在线表格 }, }, }, }, }, });如果你的版本提示createUniverSheet已废弃通常编辑器会自动提示你用新的createUnit或者createSheet方法。这种字段名和应用层 API 的调整是 Univer 目前开发阶段最让人头疼、但也最正常的现象锁版本剪裁好升级范围就行。同时记得在src/style.css里给挂载容器设置高度html, body, #app { height: 100%; margin: 0; }这一步最容易漏容器高度为 0 时画布什么都画不出来界面要么空白要么只有一条细线。3.4 跑通后在 React/Vue 里集成的基本思路Univer 本身是纯 TypeScript 实现不绑定任何前端框架React 和 Vue 都能直接用。核心思路是在组件挂载后初始化 Univer 实例组件卸载时调用univer.dispose()释放资源。这里特别提醒 React 18 开发模式的 StrictMode。StrictMode 会让组件挂载、卸载、再挂载如果 Univer 实例初始化逻辑写在组件里并且没有保护会出现容器重复挂载的警告。解决办法是给初始化加一个单例判断或者把 Univer 实例放到组件外部缓存。Vue 3 的onMounted里初始化、onUnmounted里销毁相对直接一些但也同样要小心热更新的重复执行。4. 二次开发从哪里入手命令、插件、公式和自定义绘制跑通最小 Demo 只是开始真正进到业务层你很快会需要扩展能力。这一节我把二次开发的几个关键入口捋一遍。4.1 通过 API 操作工作簿和单元格日常开发中最常见的需求是外部触发表格数据变更比如点击保存按钮读取数据或者把接口返回的数据填入表格。可以用 Univer 暴露的 API 操作const workbook univer.getActiveWorkbook(); const sheet workbook.getActiveSheet(); // 读取单元格 const cellData sheet.getCellData(0, 0); console.log(cellData); // { v: Hello } // 写入单元格并强制 UI 刷新 sheet.setCellData(0, 0, { v: 新值 });这里要注意的是直接通过 API 修改单元格时要确保改完的数据能触发视图刷新。有时候你会遇到数据改了、界面没变化的情况通常是因为没有通过正确的命令入口去改数据或者表格没有把这次修改当作“需要重绘”的操作。我的习惯是优先走命令系统去修改数据比如用sheet.setCellData这种官方提供的接口而不是直接改内部数据对象。4.2 写一个自定义插件Univer 的插件机制是扩展能力的主要入口。自定义插件通常长这样import { Plugin } from univerjs/core; export class MyBusinessPlugin extends Plugin { constructor() { super(my-business-plugin); } override onMounted(): void { // 插件挂载时初始化自己的逻辑 console.log(My plugin mounted); } override onReady(): void { // 文档就绪后执行比如注册自定义命令、监听数据变化 } override onDispose(): void { // 插件销毁时清理资源 } } univer.registerPlugin(MyBusinessPlugin);具体生命周期钩子的命名和触发时机随着版本更新会有调整但“注册插件 - 挂载钩子 - 执行逻辑”这个套路是稳定的。如果你要做的功能涉及工具栏按钮、右键菜单、快捷键一般都是在插件里通过 UI 扩展点注册进去。4.3 扩展自定义公式业务场景里经常有“这个计算 Excel 原生函数算不出来”的情况比如根据企业自定义规则计算绩效、组合字段生成编码。Univer 的公式引擎支持自定义函数注册思路是写一个函数声明参数个数和计算逻辑然后注册进公式引擎就能在单元格里像使用原生函数一样调用。因为公式引擎和 UI 是分离的这类自定义函数同样可以脱离界面在 Web Worker 里运行不会因为计算量大而卡住界面。这里给一个方向性的提示注册入口通常在univerjs/engine-formula包中具体的函数描述对象要包含函数名、参数类型、计算回调等字段。实际开发时对照你安装版本的类型定义是最准的。4.4 单元格自定义绘制这是 Univer 相对炫酷的扩展点。默认情况下单元格显示的是文本、数字、背景色但通过自定义渲染你可以在单元格里画出进度条、状态圆点、图标等任意内容。实现路径一般是基于渲染引擎的图层机制注册一个自定义绘制对象把绘制函数挂到特定单元格或特定区域上每次重绘时执行你的draw逻辑。这个功能适合做数据可视化、审批状态展示、看板类型的表格应用。不过它对 Canvas 绘制的理解要求比较高建议在熟悉 Univer 基本的渲染分层之后再去尝试否则容易出现绘制层级错乱、滚动后内容错位等问题。5. 实测盘点集成 Univer 最容易踩的五个坑下面这部分是我和团队在集成过程中真实遇到、花时间排查过的问题。提前知道这些坑能帮你省下不少时间。5.1 版本对齐是最大的隐性成本Univer 的包数量很多一个项目可能同时装十几个univerjs/*包。如果安装时没有统一版本小版本之间有突破性变化经常表现为插件注册时不报错但 UI 不生效或者控制台出现中文/英文的 store 冲突警告。解决方案用 lock 文件锁死版本升级时全量升级不要只升某一个包。package.json里对univerjs/*建议用精确版本号比如0.3.0而不是^0.3.0否则 npm 可能引入同一大版本下不同小版本的混合组合。5.2 Excel 导入导出不是百分百还原Univer 提供了import-xlsx和export-xlsx插件常规的单元格值、字体、对齐、合并单元格都能处理但复杂场景下会有还原度问题。我实测遇到过某些条件格式规则导出后丢失、自定义数字格式在导入导出后显示异常、打印分页设置不保留。如果你的业务对 Excel 文件格式的忠实度要求极高务必在选型阶段拿真实业务文件做一轮回归测试别拿只有一个表格的 demo 文件自欺欺人。5.3 自定义字体加载慢会导致文本测量错位Canvas 绘制文字时文本宽度依赖字体测量结果。如果页面引入的自定义字体还没加载完Univer 初始化时用默认字体测出来的宽度和实际渲染宽度会有偏差表现就是单元格里的文字重叠、截断或者撑出边界。规避方法在初始化 Univer 之前主动用浏览器字体 API 预加载字体await document.fonts.load(16px MyCustomFont);这个坑在普通 DOM 页面里不太明显但在 Canvas 渲染的表格里几乎是必现的。字体策略最好在项目层面统一约定避免不同页面加载不同字体导致测量误差。5.4 React StrictMode 双调用和销毁时机前面提过 React 18 开发模式下 StrictMode 会执行两次挂载逻辑。如果 Univer 初始化没有做幂等保护容器会被重复绑定控制台会出现容器已经被使用的报错。更麻烦的是如果初始化成功但销毁没做好组件反复切换后内存占用会持续上升。我的建议是把 Univer 实例管理封装成一个类或模块级别的单例提供init和dispose方法由组件的生命周期调用同时在dispose里调用 Univer 对应的销毁方法并清空容器 DOM。这样无论在 React 还是 Vue 下都能规避资源泄漏问题。5.5 协同前端准备好了后端要自己写这是很多人看宣传语容易产生错觉的地方。Univer 确实在数据模型和命令机制上为协同做好了准备但“支持协同”和“能用协同”之间有很长的路要走。实际接入多人协同你需要自己处理服务端消息转发、冲突解决策略OT 还是 CRDT、历史版本快照、权限控制、光标/选区同步 UI。我用一个表格概括协同能力现状能力方向Univer 现有支持需要自己补的部分文档数据模型支持存储、权限、快照回滚命令协议支持服务端广播、消息幂等、离线缓存冲突解决机制预留OT/CRDT 方案选型与实现在线状态无在线列表、光标同步 UI如果你的项目目标是多人在线编辑选型时要确认团队有能力做这一层服务端开发否则后续会出现“前端能力很强大后端完全接不住”的尴尬局面。6. 该不该选 Univer我的选型判断与落地建议前面讲了大量“怎么做”最后聊聊“要不要做”。选型本质上是对团队能力和业务诉求的匹配没有绝对的最好只有适不适合。6.1 适合选 Univer 的项目形态从我的经验看以下几类场景会比较适合。要做深度可控的在线表格不想被商业表格控件锁定的团队。Univer 开源、可改源码、扩展入口标准适合长期自研。已有成熟前端团队愿意承担二次开发和版本跟进成本。这类项目不是一个静态库而是需要持续跟进迭代的基础设施。业务里同时有表格、文档、幻灯片诉求想统一技术底座愿意陪着 Univer 从早期阶段一起成长。对公式计算有强诉求比如数据填报系统、企业绩效计算平台想把计算引擎独立到 Worker 或服务端运行。6.2 不建议选 Univer 的场景业务只是展示表格数据不需要复杂编辑能力。杀鸡不用牛刀一个 HTML 表格或者组件库的表格就够用了引入 Univer 反而增加体积和复杂度。对 Excel 格式兼容性要求极其苛刻要求开箱即用。这种情况还是优先看商业方案或成熟的在线 Office 协作服务省心程度远高于开源自建。团队只有两三人、项目排期又紧。让一个小团队边赶业务边啃一套 0.x 的框架风险很高。未来短期内完全没人力对接版本升级。Univer 现在还在快速变化几个月不升级就可能落后好几个大版本这个成本必须提前计算。6.3 落地时我的一点体会最后分享一个实际的心得。刚开始做 Univer 集成时我习惯先把 UI 调得很完整再去关注扩展逻辑结果踩了不少版本相关的坑。后来调整了顺序先用最小集成跑通数据读写然后立刻写一个自己的插件把一条完整的业务链路串起来——比如点击按钮读取当前选中区域的单元格并输出到控制台。这条链路跑通后再去改主题、改工具栏、调整布局都会顺利很多。因为 Univer 的核心价值不在“看起来像 Excel”而在“围绕表格的数据流你说了算”。建议你先复现这条业务链路再开始折腾外观。