Skip to content
目录

核心数据模型与 Operation 系统

1. 数据模型:DOM-like,但不等于 DOM

Slate 文档是递归树:

  • Editor(根)
  • Element(结构节点)
  • Text(叶子文本节点)

定位与选择由三元组组成:

  • Path:树路径
  • Point{ path, offset }
  • Range{ anchor, focus }

这套抽象保证了“文档结构变化后仍可追踪位置”。


2. 为什么一切都落到 Operation

packages/slate/src/interfaces/operation.ts 注释写得非常直接:

把所有变化表示为 operation,才能容易实现 history、collaboration 等能力。

源码原文(节选):

ts
/**
 * `Operation` objects define the low-level instructions that Slate editors use
 * to apply changes to their internal state. Representing all changes as
 * operations is what allows Slate editors to easily implement history,
 * collaboration, and other features.
 */

操作类型分三类:

  • Node 操作:insert_node/remove_node/move_node/set_node/split_node/merge_node
  • Text 操作:insert_text/remove_text
  • Selection 操作:set_selection

3. 执行与落盘:Transforms -> apply(op)

常见误区是把 Transforms 当作“底层”。
实际是:

  • Transforms 是用户友好层(高层 API)
  • 最终都会变成 operation
  • operation 统一交给 editor.apply(op) 执行

因此可以说:Slate 不是命令类模式,而是“数据化命令(operation object)+ 统一执行器(apply)”


4. 可逆性:Operation.inverse

Operation.inverse(op) 是历史和协作能力的关键基础:

  • insert_text <-> remove_text
  • insert_node <-> remove_node
  • set_node 交换 properties/newProperties
  • set_selection 反转 old/new 选区
  • move_node 做路径修正,处理非 sibling move 的路径漂移

历史插件 slate-history 的 undo 正是基于这里逆序回放。


5. 源码证据

5.1 operation 类型定义和校验

文件:packages/slate/src/interfaces/operation.ts

  • Operation.isOperation 做运行时结构校验
  • Operation.isOperationList 用于历史栈校验

5.2 apply 主干

文件:packages/slate/src/core/apply.ts

执行顺序非常重要:

  1. ref transform(PathRef/PointRef/RangeRef)
  2. dirty path 更新
  3. Transforms.transform(editor, op) 真正改文档
  4. push editor.operations
  5. normalize
  6. flush onChange

对应代码(节选):

ts
Transforms.transform(editor, op)
editor.operations.push(op)
Editor.normalize(editor, { operation: op })

if (!FLUSHING.get(editor)) {
  Promise.resolve().then(() => {
    editor.onChange({ operation: op })
    editor.operations = []
  })
}

6. 与协作系统(OT/CRDT)的接口意义

Slate 本身不内建协作协议,但 operation 的数据化让你可以:

  • 序列化并传输变化
  • 回放远端变化
  • 与外部 OT/CRDT 层对接

官方概念文档 docs/concepts/05-operations.md 也明确把这作为设计目标。


7. 相关 issue/讨论(设计依据)

  • Issue #3741move_node 信息与协作可逆性的关系
  • Issue #5977isOperation 与自定义 operation 的边界

8. 答法要点(30 秒)

Slate 的核心抽象不是 DOM 事件,而是 operation。
高层变换 API 最终都产出 operation,apply 负责统一执行和规范化。
这种设计的收益是:历史、协作、回放、调试都能围绕同一数据模型开展,不会被 React 或浏览器事件耦死。

MIT License.