Skip to content
目录

Editor 生命周期与事务管线

1. 生命周期主线

Editor 的关键阶段可以记成:构造 -> mount -> 事务循环 -> unmount/destroy

1.1 构造阶段

packages/core/src/Editor.ts 中,构造函数会依次:

  • 合并 options(默认开启 core extensions、input/paste rules)。
  • createExtensionManager():解析扩展、建 schema、绑定扩展生命周期事件。
  • createCommandManager():准备 commands/chain/can
  • 解析初始内容创建 EditorState(即使没挂载 DOM)。

1.2 挂载阶段

mount(el) 会创建 EditorView,并将扩展提供的:

  • dispatchTransaction 包装链
  • transformPastedHTML 变换链
  • nodeViews/markViews
  • plugins(reconfigure 注入)

挂载后异步触发 create 事件,并根据 autofocus 聚焦。

1.3 卸载/销毁阶段

  • unmount():销毁 view、清理 DOM 上的 editor 引用、按条件移除注入 CSS。
  • destroy():触发 destroy 事件并 removeAllListeners()

2. 事务分发管线(高频要点)

核心函数:Editor.dispatchTransaction(transaction)

2.1 执行步骤

  1. 若 view 已销毁,直接返回。
  2. 若处于 captureTransaction,把多个 step 合并到捕获事务。
  3. 调用 state.applyTransaction(transaction) 得到:
    • next state
    • transactions(包含 appended transactions)
  4. 触发 beforeTransaction
  5. 若根事务被 filter 掉(!rootTrWasApplied),提前返回。
  6. view.updateState(state)
  7. 触发事件:
    • transaction
    • selectionUpdate(选择区变化时)
    • focus/blur(读取 transaction meta)
    • update(仅 doc 真正变化且未 preventUpdate

2.2 关键设计

  • 事件分层清晰transactionupdate 分开,避免每个事务都当内容更新处理。
  • 兼容 appended transactions:插件追加事务不会丢上下文。
  • 显式控制更新噪音preventUpdate 可抑制无意义 re-render。

源码片段(packages/core/src/Editor.ts):

ts
const { state, transactions } = this.state.applyTransaction(transaction)
const rootTrWasApplied = transactions.includes(transaction)

if (!rootTrWasApplied) {
  return
}

this.view.updateState(state)
this.emit('transaction', { transaction, appendedTransactions: transactions.slice(1) })

if (transaction.getMeta('preventUpdate') || !transactions.some(tr => tr.docChanged)) {
  return
}
this.emit('update', { transaction, appendedTransactions: transactions.slice(1) })

3. view 代理机制:为什么未挂载也能工作

Editor.view 在未挂载时返回一个 Proxy:

  • 持有 state/updateState/dispatch 的最小可用能力。
  • 访问未知字段时抛出明确错误(避免 silent failure)。

价值:

  • SSR 或“延迟挂载”场景下,命令与状态访问仍可预测。
  • 降低框架层(React/Vue)对挂载时序的耦合。

4. CSS 注入策略与多实例坑

injectCSS 默认注入一次样式到 head
问题是多实例场景:销毁一个编辑器不应影响其他实例。

4.1 演进依据

  • Issue: #6836
  • PR: #7109
  • 现有实现:unmount 时仅在文档中不存在其他 .tiptap 编辑器元素才移除 CSS。
  • 单测:packages/core/__tests__/unmounted.spec.ts 已覆盖双实例场景。

这体现了 Tiptap 对“同页多编辑器”作为一等场景的修正。

5. 内容容错与协同安全阀

createDoc() 会在内容非法时触发 contentError

  • 默认可抛错,也可选择“剥离非法内容继续启动”。
  • 若启用协同,支持通过 disableCollaboration 避免非法内容反向同步到远端。

这是典型的“编辑器可用性 > 严格失败”的工程权衡。

MIT License.