Skip to content
目录

Transforms 与 Normalization 机制

1. Transforms 是“语义 API”,不是最终执行层

Transforms.insertNodes / setNodes / moveNodes / select 这些 API 本质是便捷层。
真正落地由 editor.apply(op) 驱动。

官方文档 docs/concepts/04-transforms.md 也明确:复杂 transform 会拆成多个低层 operation。


2. Normalization 的本质:自动修复而非仅校验

Slate 的 normalize 思路是“发现不合法结构 -> 立即修复”,而不是返回 boolean。

默认内建约束(见 docs/concepts/11-normalizing.md)包括:

  1. Element 必须至少有一个 Text 后代
  2. 相邻同格式 Text 会合并
  3. block/inline 子节点结构必须一致
  4. inline 不能孤立在头尾或紧邻 inline
  5. editor 顶层只能是 block

3. 关键实现:normalizeNode + 脏路径循环

3.1 默认 normalizer

文件:packages/slate/src/core/normalize-node.ts

核心逻辑:

  • 非文本节点缺少 children 时补齐
  • 空元素插入空文本子节点
  • inline 周围补空文本
  • 相邻文本可合并时 merge
  • 非法 block/inline 混合时 unwrap/remove/wrap

源码注释(节选):

ts
// Ensure that elements have at least one child.
if (element !== editor && element.children.length === 0) {
  Transforms.insertNodes(editor, { text: '' }, { at: path.concat(0), voids: true })
}

// Ensure that inline nodes are surrounded by text nodes.

3.2 normalize 循环调度

文件:packages/slate/src/editor/normalize.ts

  • 维护 dirty path 队列
  • 先跑“空 children”早期修复(避免后续规则失效)
  • 多轮循环调用 editor.normalizeNode
  • 每轮可能新增 dirty path(多阶段修复)

4. 为什么会有 withoutNormalizing

批量变换时,中间态常常不合法。
如果每步都 normalize,可能:

  • 性能差(重复修复)
  • 逻辑错(中间态被提前修复导致后续步骤失效)

所以要用:

ts
Editor.withoutNormalizing(editor, () => {
  // 多个 transforms
})

文档与 issue 都强调这一点:

  • Issue #3467:名字容易误解,withoutNormalizing 是“延迟”,不是“永不规范化”
  • Issue #2658:社区讨论 transaction 风格 API

5. 防死循环:shouldNormalize 与迭代上限

文件:packages/slate/src/core/should-normalize.ts

  • maxIterations = initialDirtyPathsLength * 42
  • 超过阈值抛错:Could not completely normalize...

源码(节选):

ts
const maxIterations = initialDirtyPathsLength * 42 // HACK: better way?
if (iteration > maxIterations) {
  throw new Error(
    `Could not completely normalize the editor after ${maxIterations} iterations! ...`
  )
}

这反映了一个现实权衡:
Normalization 是可扩展且递归触发的,必须有“刹车机制”防止错误 normalizer 造成无限循环。

相关讨论:


6. 初始值 normalize 的历史争议

是否自动对 initialValue 做全量 normalize 长期有争议:

  • Issue #3465 提到“初始值不立即全量规范化”的行为影响
  • 当前测试里可以看到 Editor.normalize(editor, { force: true }) 的显式路径(packages/slate/test/index.js

可说:Slate 更倾向把 normalize 触发绑定到操作路径,而不是在所有时机做昂贵全量修复。


7. 关键得分点

Slate 的 normalize 是 multi-pass 修复系统。你写自定义 normalize 时应当“一次只修一个问题并 return”,让下一轮继续。
复杂命令要用 withoutNormalizing 包裹,避免中间态被提前修复。
同时要避免错误修复导致无限循环,Slate 通过迭代上限做保护。

MIT License.