Univer拆解——从类与方法的视角分析

最近在学习Univer这个开源项目,边学边整理为笔记。本篇主要从面向对象的角度出发,通过拆解Univer Sheet中的各种类,以及它们的关系,来进一步掌握项目架构。

项目链接:github.com/dream-num/u...

数据对象

UnitModel

packages/core/src/common/unit.ts

唯一一个真正的抽象基类

ts 复制代码
export abstract class UnitModel<D = object, T extends UniverInstanceType = UniverInstanceType> 
extends Disposable {
    abstract readonly type: T;
    abstract getUnitId(): string;
    abstract name$: Observable<string>;
    abstract setName(name: string): void;
    abstract getSnapshot(): D;
    abstract getRev(): number;
    abstract incrementRev(): void;
    abstract setRev(rev: number): void;
}

而Workbook,即Univer Sheet项目中的工作区,继承了该基类

Workbook

聚合根(Aggregate Root) 是 DDD 的说法:外部只能通过它来访问内部对象,它保证内部一致性。Workbook 就是表格领域的聚合根。

Workbook可以理解为一整本Excel表格文件在内存中的对象。Workbook中有两个重要对象,分别是snapshot和worksheet。Snapshot是数据快照,可以理解为形如json形状的一种数据,用来存盘,拷贝,协作时发送给服务器等。例如下面的这种形态。

json 复制代码
{
  id, name, styles,
  sheetOrder: ['sheet1', 'sheet2'],
  sheets: {
    sheet1: { name: 'Sheet1', cellData: {...}, ... },
    sheet2: { name: 'Sheet2', ... }
  }
}

而worksheet则是workbook中的一张工作表。可以理解为是运行时缓存snapshot的镜像。worksheet也是一个类,在运行时中其相关方法会被调用。当然,worksheet也有自己sheet级的snapshot。

typescript 复制代码
worksheets: Map<string, Worksheet>  ← ★ 运行时对象缓存snapshot 的镜像
名字 像什么 干什么
Workbook 整个 .xlsx 管多张表、激活表、整本样式、保存
snapshot 文件内容/JSON 存、传、协作同步的"真相数据"
Worksheet 某一个 Sheet 页 管这一页的格子和行列,是对象,有方法

当用户对表格进行改动时,底层的数据对象的变化是一致的。即workbook的snapshot.sheet和worksheet的snapshot是同一份数据。

js 复制代码
workbook._snapshot.sheets[id]  ──┐
                                 ├──►  { id, name, cellData, ... }   ← 同一块
worksheet._snapshot            ──┘
                                      cellData ──►  {}
                                                    ▲
worksheet._cellData._matrix  ───────────────────────┘  也是同一块

Worksheet

worksheet是整个表格领域访问最频繁的构造。当需要查询表格单元格内容,单元格行列宽度,是否合并,最终样式等等,都需要落脚到worksheet。worksheet是sheet表的操作入口,底层数据在snapshot中。worksheet中的方法往往和表格数据的读写相关。例如下所示

js 复制代码
写:getCellMatrix().setValue(...)  
读原始单元格数据:getCellRaw
读被拦截后的显示:getCell
读最终样式:getComposedCellStyle
合并:getMergedCell / getSpanModel
区域:getRange(...)

但是不是所有交互都改 Worksheet。例如选区高亮、悬浮、部分 UI 面板状态,可能只在 sheets-ui / 渲染层,不一定写进 Worksheet。除此之外,有的交互落在 Workbook,不在某一张 Worksheet。比如新建/删除/重命名表、工作簿级样式池、dateSystem 等,改的是 Workbook。

拦截器

在worksheet中,有好几种读取单元格内容的方法。getcell,getcellraw等等。为什么会有这些呢,是因为在不同场景下,我们对读取单元格内容的形式要求不同。换句话说,单元格的显示和单元格数据模型中真正存的数据可能不同。

比如,用户在 A1 输入 0.5,并把格式设成百分比。

  • 存盘(raw):大致是 { v: 0.5, t: NUMBER, s: '...' }
  • 屏幕上应显示:50%

格式化结果一般不写回真 cellData(否则存盘会变成字符串、协作/计算也麻烦)。所以在读的时候拦截。

「拦截」就体现在:走 getCell,不走 getCellRaw。中间被插进了一段逻辑

worksheet方法,有下面几种getcell方法

js 复制代码
// 读原始数据
getCellRaw(row: number, col: number): Nullable<ICellData> {
        return this.getCellMatrix().getValue(row, col);
}

// 读viewModel的数据,相当于做了一下拦截,没有直接读raw
getCell(row: number, col: number): Nullable<ICellDataForSheetInterceptor> {
    return this._viewModel.getCell(row, col);
}

viewmodel方法,下面代码体现了拦截器

js 复制代码
getCell(...) {
    if (this._cellContentInterceptor) {
        return this._cellContentInterceptor.getCell(...); // 拦在这里
    }
    return this.getRawCell(row, col);
}
相关推荐
俊昭喜喜里7 分钟前
理解全局跨域CORS配置
前端
香芋芋圆36 分钟前
iD-editor 详解|OSM官方在线编辑器,基于D3.js的轻量地图编辑器
开发语言·javascript·编辑器
晗夜未央2 小时前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
锋行天下3 小时前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan3 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
计算机魔术师4 小时前
Anthropic官方插件被「寄生」,背后是同一类攻击又来了
前端
我命由我123454 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
key_3_feng4 小时前
评估与对齐:Agent 自我进化的护栏
前端·数据库·agent
程序员大阳4 小时前
副队长JavaScript教程(65)-详解JSON:轻量级数据交换格式
javascript·json·序列化·反序列化·stringify·parse
LaughingZhu5 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion