Univer 单元格批注核心包 @univerjs/sheets-note 使用与架构深度解析
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
@univerjs/sheets-note是 Univer 表格(Sheets)生态中负责单元格批注(Note)核心模型与命令的插件包,为单元格提供增删改查、显隐切换、撤销重做、随行列变更联动以及文档快照序列化等能力。本文以该包的 README 为主线,结合 packages/sheets-note 下的真实源码,讲解从安装接入、数据模型、命令系统到 Facade API 的完整链路,帮助你理解批注功能在 Univer 中如何被组织与扩展。
包概览:定位与边界
根据 README 中的包概览表,@univerjs/sheets-note的关键属性如下:
| Package | UMD global | CSS | Locales | Facade entry |
|---|---|---|---|---|
@univerjs/sheets-note | UniverSheetsNote | No | No | Yes |
从中可以提炼出三点定位信息:
- 纯核心层:该包不携带任何 CSS,也没有自己的多语言资源(Locales),说明它只负责批注的"数据与逻辑"层,不渲染任何界面。
- 提供 Facade 入口:
Facade entry = Yes,意味着它向 Univer 的 Facade(univerAPI)注入批注相关的 API 与事件,方便开发者用简洁的命令式接口操作批注。 - 与 UI 包分工明确:README 的 Integration Notes 明确指出,需要编辑界面时应搭配
@univerjs/sheets-note-ui使用——核心包管数据,UI 包管交互呈现。
安装与版本一致性
README 给出的安装方式非常直接:
pnpm add @univerjs/sheets-note # or npm install @univerjs/sheets-note同时 README 强调了一条对 Univer 全家桶通用的铁律:保持所有@univerjs/*包版本一致(Keep all@univerjs/*packages on the same version),避免因各包版本漂移导致接口不匹配。这一点在 monorepo 工作区(pnpm-workspace.yaml)和示例工程中均可看到遵循同一版本约束的实践。
快速接入:注册插件
README 展示了最小接入代码:
import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'; univer.registerPlugin(UniverSheetsNotePlugin);从源码 plugin.ts 可以看出该插件更多细节:
- 插件名为
SHEET_NOTE_PLUGIN(见 const.ts),声明为UniverInstanceType.UNIVER_SHEET类型,仅服务于表格单元。 - 通过
@DependentOn(UniverSheetsPlugin)声明依赖@univerjs/sheets主包,注册时会自动校验并确保表格核心插件已就绪。 - 配置项类型为
IUniverSheetsNoteConfig,当前在 config/config.ts 中为空接口,默认配置defaultPluginConfig为空对象,插件配置会以sheets-note.config为 key 写入IConfigService,为后续扩展保留入口。
插件在onStarting阶段向依赖注入容器注册了 4 个核心依赖并立即实例化前 3 个:
| 依赖 | 职责 |
|---|---|
SheetsNoteModel | 批注内存数据模型与变更事件流 |
SheetsNoteController | 注册批注相关命令与 mutation |
SheetsNoteResourceController | 快照序列化、工作表删除/复制联动 |
SheetsNoteRefRangeController | 行列插入/删除时批注位置联动(onReady阶段实例化) |
数据模型:ISheetNote 与 SheetsNoteModel
批注的数据结构定义在 sheets-note.model.ts:
export interface ISheetNote { id: string; // 批注唯一 ID row: number; // 所在行(0 基索引) col: number; // 所在列(0 基索引) width: number; // 批注展示宽度 height: number; // 批注展示高度 note: string; // 批注文本内容 show?: boolean; // 是否显示(控制弹出/收起) }SheetsNoteModel是批注的唯一数据源,采用三级Map结构组织数据:unitId(工作簿)→ subUnitId(工作表)→ noteId → ISheetNote。围绕它提供了一组完整操作 API:
updateNote(unitId, subUnitId, row, col, note, silent?):新建或更新批注。若没有传入id,会用generateRandomId(6)自动生成 6 位随机 ID。removeNote(...):按noteId或(row, col)删除批注。toggleNotePopup(...):切换批注的show状态,用于弹出/收起气泡。updateNotePosition(...):将批注移动到新的(newRow, newCol)。getNote(...):按 ID 或行列坐标查询单条批注;getSheetNotes(...)、getUnitNotes(...)、getNotes()分别按工作表、工作簿、全量维度取数。
模型还对外暴露了基于 RxJS 的响应式流:change$(全部变更)、getSheetShowNotes$(unitId, subUnitId)(某工作表所有处于显示状态的批注)以及getCellNoteChange$(unitId, subUnitId, row, col)(某单元格批注的变更)。所有变更都会发出ISheetNoteChange,其中type: 'update'表示常规更新,type: 'ref'表示因引用范围变化而移动位置——这两种类型正是 Facade 事件与 UI 重绘的输入源。
命令与撤销重做:Command/Mutation 双层设计
与 Univer 整体架构一致,批注的写操作遵循Command → Mutation → Model的调用链:Command 负责业务意图与撤销栈管理,Mutation 负责真正落地到 Model。相关实现位于 note.command.ts 与 note.mutation.ts。
三条业务命令:
| 命令 | ID | 行为 |
|---|---|---|
SheetUpdateNoteCommand | sheet.command.update-note | 更新/新建批注,接受{ unitId, sheetId, row, col, note }参数 |
SheetDeleteNoteCommand | sheet.command.delete-note | 删除当前选中单元格的批注 |
SheetToggleNotePopupCommand | sheet.command.toggle-note-popup | 显示/隐藏当前选中单元格批注的气泡 |
四个底层 Mutation:
| Mutation | ID | 作用 |
|---|---|---|
UpdateNoteMutation | sheet.mutation.update-note | 落库更新/新建 |
RemoveNoteMutation | sheet.mutation.remove-note | 落库删除 |
ToggleNotePopupMutation | sheet.mutation.toggle-note-popup | 落库切换显示状态 |
UpdateNotePositionMutation | sheet.mutation.update-note-position | 落库移动位置 |
以SheetUpdateNoteCommand为例,其 handler 会先通过getSheetCommandTarget解析当前命令作用的工作簿与工作表,取旧批注构造 redo/undo 的 mutation 对,用commandService.syncExecuteCommand同步执行 redo mutation,成功后通过undoRedoService.pushUndoRedo入栈。注意一个细节:若旧批注存在,undo 用UpdateNoteMutation还原;若不存在(即纯新建),undo 则用RemoveNoteMutation兜底删除。而SheetDeleteNoteCommand中删除命令的 redo 只携带noteId,说明按 ID 删除即可精准定位,无需行列坐标。所有命令都在 sheets.note.controller.ts 中统一注册进ICommandService。
与工作表操作的联动:资源快照与拦截器
批注要能随文档保存、随工作表删除而清理、随工作表复制而复制,靠的是 sheets-note-resource.controller.ts 的两个机制:
1. 插件资源快照(Snapshots)。控制器通过IResourceManagerService.registerPluginResource注册名为SHEET_NOTE_PLUGIN的资源处理器,toJson把当前工作簿的全部批注序列化为{ sheetId: { row: { col: note } } }结构的 JSON,parseJson反向解析;onLoad时逐条回填到 Model,onUnLoad时清空该工作簿的批注缓存。这让批注得以参与 Univer 统一的文档快照/加载流程。
2. 工作表命令拦截(Intercept)。控制器通过SheetInterceptorService.interceptCommand监听两条命令:
RemoveSheetCommand(删除工作表):为被删除工作表上的每条批注追加一个RemoveNoteMutation(redo)和对应的UpdateNoteMutation(undo),实现删除工作表时自动清理批注且可撤销。CopySheetCommand(复制工作表):为源表的每条批注生成一份拷贝写入目标表,并用generateRandomId(6)生成新 ID 避免与源批注冲突,undo 时逐条移除。
行列变更时的位置联动:RefRange 控制器
当用户插入/删除行或列时,批注必须跟随单元格一起移动,否则会出现"批注挂在旧坐标"的错误。这一职责由 sheets-note-ref-range.controller.ts 承担:它基于@univerjs/sheets提供的RefRangeService监听范围变化,对每个批注坐标注册 watcher;当引用范围变化后:
- 若单元格仍存在(
resultRange有效),生成UpdateNotePositionMutation把批注移动到新的(startRow, startColumn); - 若目标范围消失(如整行删除导致批注所在单元格被移除),则生成
RemoveNoteMutation删除批注,undo 中保留UpdateNoteMutation以便恢复。
这套机制与表格自身的引用范围(ref-range)联动逻辑复用同一基础设施,保证批注在行/列增删场景下的数据一致性。
Facade API 与事件:面向二次开发者的入口
@univerjs/sheets-note提供两处 Facade 扩展:
1. 工作表级查询 API。在 f-worksheet.ts 中通过 mixin 为FWorksheet增加getNotes()方法,返回当前工作表全部批注数组。README 对应的官方示例思路如下:
const fWorkbook = univerAPI.getActiveWorkbook(); const fWorksheet = fWorkbook.getSheetByName('Sheet1'); if (!fWorksheet) return; const notes = fWorksheet.getNotes(); notes.forEach((item) => { const { row, col, note } = item; console.log(`Cell ${fWorksheet.getRange(row, col).getA1Notation()} has a note: ${note}`); });2. 命令级事件钩子。在 f-univer.ts 中,FUniverSheetsNoteMixin基于model.change$与commandService.beforeCommandExecuted双向打通事件系统,注册了成对的前置(Before)与后置事件:
| 事件类别 | 事件 |
|---|---|
| 新增 | BeforeSheetNoteAdd/SheetNoteAdd |
| 更新 | BeforeSheetNoteUpdate/SheetNoteUpdate |
| 删除 | BeforeSheetNoteDelete/SheetNoteDelete |
| 显示 | BeforeSheetNoteShow/SheetNoteShow |
| 隐藏 | BeforeSheetNoteHide/SheetNoteHide |
前置事件支持"可取消"语义:当监听器触发fireEvent返回 true 时,抛出CanceledError中断命令执行(例如在BeforeSheetNoteAdd中拦截非法内容的写入)。后置事件则携带workbook、worksheet、row、col、note/oldNote等完整上下文,供业务方做审计、同步或自定义展示逻辑。
与 sheets-note-ui 的分工协作
如 README 的 Integration Notes 所述,@univerjs/sheets-note应与@univerjs/sheets-note-ui搭配使用以提供编辑 UI。二者关系可概括为:
- 核心包:定义
ISheetNote数据结构、SheetsNoteModel模型、命令/mutation、快照序列化、ref-range 联动与 Facade 事件——这是批注能力的"大脑"。 - UI 包:负责批注气泡的渲染、编辑框、右键菜单入口、快捷键绑定等交互层——这是批注的"手脚",通过消费核心包的命令与事件流实现界面与数据的同步。
即使只注册核心包,批注的数据能力(命令、撤销重做、保存恢复、行列联动)依然完整可用,这为需要自研批注界面的团队提供了干净的扩展基座。
测试保障
包内 src 各目录均配有单元测试,可作为理解行为契约的补充材料:模型层有 sheets-note.model.spec.ts,命令层有 note.command.spec.ts,控制器层有 sheets-note-resource.controller.spec.ts 与 ref-range.controller.spec.ts,Facade 层有 sheets-note.facade.spec.ts,覆盖了从数据读写到命令撤销、资源快照、范围联动与 API 事件的全链路行为。
小结
@univerjs/sheets-note以"小而专"的方式解决了表格批注的核心数据问题:SheetsNoteModel提供内存模型与响应式事件,Command/Mutation 双层命令体系保证可撤销重做,资源控制器让批注随文档保存与工作表增删联动,ref-range 控制器应对行列变更,Facade 层则向业务侧暴露简洁的 API 与可取消事件。理解这一分层,无论是直接接入批注功能,还是在其基础上构建自定义批注 UI,都能做到心中有数。
【免费下载链接】univerUniver is a full-stack framework for creating and editing spreadsheets / word processor / presentation on both web and server.项目地址: https://gitcode.com/GitHub_Trending/un/univer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考