命令系统与链式 API
1. 命令系统的目标
Tiptap 对 ProseMirror command 做了三层统一:
editor.commands.xxx():直接执行。editor.chain().xxx().yyy().run():批量串联、一次 dispatch。editor.can().xxx():可执行性预检查,不改状态。
核心实现:packages/core/src/CommandManager.ts。
2. commands:单命令执行
CommandManager.commands 做的事:
- 拿到当前
tr与 command props。 - 调用 raw command,获取回调结果。
- 若未设置
preventDispatch且非 customState,则view.dispatch(tr)。
这种设计把“命令逻辑”和“何时 dispatch”解耦,支持 can/chain 复用同一套 command 定义。
3. chain:事务聚合
createChain(startTr?, shouldDispatch=true):
- 所有 chained command 都操作同一个
tr。 - 每个 command 返回布尔值,存入 callbacks。
run()时统一 dispatch,并返回every(callback === true)。
好处:
- 避免多次 dispatch 带来的性能与中间态复杂度。
- 保持链式语义清晰(focus -> toggle -> scrollIntoView)。
4. can:无副作用探测
createCan() 的关键是把 dispatch 置为 undefined,并复用 command 执行路径。
这让 can() 不是简单“静态判断”,而是“以当前状态模拟执行并看结果”。
5. createChainableState:让状态和事务保持同步视图
位置:packages/core/src/helpers/createChainableState.ts
该函数返回一个“可链式状态对象”:
selection/doc/storedMarks来自 transaction 的 getter。- 每次取
tr都刷新这些 getter 读值。
意义:
- 在一条 chain 内,多命令之间读取的 state 是“随着 tr 演进的最新值”。
- 避免命令误用旧 state 导致行为偏差。
6. preventDispatch 与 preventUpdate
常见元数据开关:
preventDispatch:命令层阻止当前 tr 被自动 dispatch(常用于协同 undo/redo 包装)。preventUpdate:事务已 dispatch,但抑制update事件(减少不必要 UI 响应)。
答题建议:强调这是“事务传播层”和“UI 事件层”的分离控制。
7. 与输入规则/粘贴规则的联动
Issue: #5046
背景:insertContent/insertContentAt 原本与输入规则/粘贴规则联动不足。
改进后支持可选触发规则,适合“流式逐字输入 markdown”场景。
这说明命令系统不是封闭层,而是和规则系统协同演进。