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);
}
相关推荐
对空六课2 小时前
Vue 组件曝光埋点为什么会重复触发?去重方案与实现思路
服务器·前端·算法·数据分析
羲云2 小时前
不想再让用户手写 Markdown:零依赖的所见即所得编辑器最新版发布 V0.2.0
前端
deli0072 小时前
限流算法到底怎么选?令牌桶 vs 漏桶 vs 滑动窗口 3 种同屏实测
前端
陈前端2 小时前
AI 猜错了接口返回格式之后,我开始怀疑喂给它的上下文
前端
风花一世月2 小时前
🏫 我把一整座校园的数据大屏塞进了一个 HTML 文件(零构建、双击即开,在线直接玩)
前端
变与不变8062 小时前
GET请求知识详解
前端·javascript
智塑未来2 小时前
网站建设哪家服务好?把交付前、交付中、交付后拆开看
大数据·前端
百度一下吧2 小时前
H5 应用开发:Android、iOS 与鸿蒙安全区域适配实战
前端