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 执行步骤
- 若 view 已销毁,直接返回。
- 若处于
captureTransaction,把多个 step 合并到捕获事务。 - 调用
state.applyTransaction(transaction)得到:- next state
- transactions(包含 appended transactions)
- 触发
beforeTransaction。 - 若根事务被 filter 掉(
!rootTrWasApplied),提前返回。 view.updateState(state)。- 触发事件:
transactionselectionUpdate(选择区变化时)focus/blur(读取 transaction meta)update(仅 doc 真正变化且未preventUpdate)
2.2 关键设计
- 事件分层清晰:
transaction与update分开,避免每个事务都当内容更新处理。 - 兼容 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避免非法内容反向同步到远端。
这是典型的“编辑器可用性 > 严格失败”的工程权衡。