ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Slate v2 扩展生命周期上下文契约:为每个回调定义 state / tx / editor 的职责边界

Slate v2 扩展生命周期上下文契约:为每个回调定义 state / tx / editor 的职责边界 Slate v2 扩展生命周期上下文契约为每个回调定义 state / tx / editor 的职责边界【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文基于仓库中的规划文档 2026-05-18-slate-v2-extension-context-state-tx-coverage-ralplan.md完整解析 Slate v2 扩展extension上下文对象的设计裁决读回调拿state更新回调拿tx提交后回调拿commit/snapshot长生命周期注册回调拿editor。读完本文你将掌握 Slate v2 扩展系统中 query / transform / clipboard / normalizer / operation / commit 各生命周期回调的上下文形状、前后对比代码、决策背后的权衡依据以及配套的负向类型测试与验证命令可直接用于扩展插件开发与 API 使用。为什么需要一份上下文契约Slate v2 的扩展系统暴露了多组入口editor.read、editor.update、editor.api以及各类生命周期对象。插件作者在写一个回调时经常要停下来猜测这里到底该调用editor.read、editor.update、editor.api还是一个生命周期对象上的方法这份 Ralplan 的核心意图Intent正是回答这个问题为 Slate v2 的每一个扩展回调定义上下文对象让用户不再猜测该调用editor.read、editor.update、editor.api还是某个生命周期对象。规划文档给出的总体裁决Verdict非常明确是extension.queries应该收到state。否transform 中间件不应以state作为主上下文。deleteBackward、deleteForward、insertBreak、insertText以及其余transforms都属于更新生命周期钩子目标形状是transforms.*({ tx, next, ...args })——tx已经同时包含读方法与写方法。关键前置条件当前源码还不能保证每次 transform 中间件调用都运行在更新事务update transaction内因此必须在修复调度路径之后才向 transform 中间件暴露tx。否则现在实现容易未来永远更糟。规划文档标记为Status: done、Score: 0.97 implemented并在 Ralph Execution Ledger 中记录了从 tdd-pass 到 final-gates 的完整执行闭环详见下文实施与验证部分。最终落地的 Outcome一条生命周期一种上下文规划文档定义的目标结果Outcome可概括为六条读生命周期回调query 中间件获得state更新生命周期回调transform 中间件获得tx提交后回调commit listeners获得commit与snapshot注册与长生命周期扩展 API获得editoroperation 中间件保持底层化不获得tx不提供根级editor.state快捷方式——它看起来漂亮但会制造陈旧读取stale-read压力。同时明确 Non-Goals避免读者误解这份规划的边界本 Ralplan 不修改 Slate v2 实现之外的东西不提供 Plate 兼容层不为旧的Editor.*、DOMEditor.*、HistoryEditor.*辅助命名空间提供公开兼容别名不在公开文档中写迁移说明——这份规划是实现指引不是迁移指南。生命周期覆盖总览每个表面该拿什么规划文档用一张生命周期覆盖映射表逐一裁定每个扩展表面的当前上下文与目标上下文Surface当前上下文生命周期目标上下文裁决editor.read回调获得stateread保持statekeepeditor.update回调获得名为tx的 transactionupdate保持txkeepextension.statefactory(state, editor)读组构造保持(state, editor)keepextension.txfactory(transaction, editor)更新组构造参数示例改名为txkeepextension.editorfactory(editor)长生命周期 helper 构造保持(editor)除非必要避免公开示例keepextension.api静态 API 对象长生命周期 API 命名空间保留于editor.api与editor.getApi(extension)keepextension.transforms.*{ editor, next, ...args }更新中间件事务路由修复后改为{ tx, editor, next, ...args }reviseextension.queries.*{ editor, next, ...args }读中间件{ state, editor, next, ...args }reviseextension.normalizers.editor/node受限tx更新规范化保持受限txkeepextension.clipboard.insertData(data, { editor, next })DOM/DataTransfer 入口常为异步(data, { state, editor, next })不给txreviseextension.operationMiddlewares({ editor, operation }, next)operation 分发管线保持无state/txkeepextension.commitListeners(commit, snapshot)提交后保持无state/txkeepextension.register{ editor, name, options, runtimeState, signal }安装生命周期保持无state/txkeepextension.elements声明式 specschema/spec 注册保持无上下文keepeditor.subscribe监听器接收 snapshot 更新路径提交后订阅保持无state/txkeep内部命令注册表带editor的命令上下文内部分发不暴露为公开 DX保持内部内部 capabilities 注册表支撑 clipboard 与旧式通道内部运行时注册表不暴露公开capabilities保持内部或后续收缩规划文档的源码证据Current Source Evidence指向 Slate v2 源码树.tmp/slate-v2规划执行期的实时源码位置例如.tmp/slate-v2/packages/slate/src/interfaces/editor.ts:466定义EditorCoreStateView读组:484定义EditorCoreUpdateTransaction读组 写组:505暴露editor.api、editor.getApi、editor.read、editor.update、editor.extend:1210已为 normalizer 提供受限tx:1338列出扩展槽位api、clipboard、commitListeners、editor、elements、normalizers、operationMiddlewares、queries、state、transforms、tx。说明当前仓库快照中.tmp目录仅保留发布状态文件v2 源码树未随本快照检出上述路径为规划文档自述的证据位置可作为 API 设计依据阅读。逐表面改造Before / After 代码形状规划文档为四个关键表面提供了完整的改造前后代码这些代码是理解契约的最直接素材。1. Query 中间件获得state当前queries: { text: { string({ at, next, options }) { return ${next({ at, options })}! }, }, }目标queries: { text: { string({ at, next, options, state }) { const selection state.selection.get() if (!selection) { return next({ at, options }) } return ${next({ at, options })}! }, }, }规则用next完成被拦截查询的延续用state做相邻读取。不要让state.text.string(...)魔法般地绕过同一中间件——那会把递归隐藏起来而不是教会读者中间件模型。2. Transform 中间件获得事务内tx当前checklist 示例中反复editor.readeditor.updatedeleteBackward({ editor, next }) { const selection editor.read((state) state.selection.get()) if (selection RangeApi.isCollapsed(selection)) { const match editor.read((state) state.nodes.find({ match: (n) NodeApi.isElement(n) n.type check-list-item, }) ) if (match) { const [, path] match const start editor.read((state) state.points.start(path)) if (PointApi.equals(selection.anchor, start)) { editor.update((tx) { tx.nodes.set({ type: paragraph }) tx.selection.set(start) }) return } } } next() }目标deleteBackward({ tx, next, unit }) { const selection tx.selection.get() if (selection RangeApi.isCollapsed(selection)) { const match tx.nodes.find({ match: (n) NodeApi.isElement(n) n.type check-list-item, }) if (match) { const [, path] match const start tx.points.start(path) if (PointApi.equals(selection.anchor, start)) { tx.nodes.set( { type: paragraph } satisfies PartialSlateElement, { match: (n) NodeApi.isElement(n) n.type check-list-item, } ) tx.selection.set(start) return } } } next({ unit }) }实现门禁Implementation gate该目标形状只有在transform 中间件执行与默认 transform 处于同一事务时才是合法的。当前executeTransformMiddleware调用executeCommand时没有传implicitUpdate因此这不只是类型编辑而是一个真实的事务路由改造对应规划中的第一阶段。3. Clipboard 中间件state可读、tx禁止当前clipboard: { insertData(data, { editor, next }) { // parse DataTransfer editor.update((tx) { tx.fragment.insert(fragment) }) return true }, }目标clipboard: { insertData(data, { editor, next, state }) { const selection state.selection.get() if (!selection) { return next() } editor.update((tx) { tx.fragment.insert(fragment) }) return true }, }这里没有tx。剪贴板处理器位于 DOM/DataTransfer 边界可能跨越异步FileReader或上传边界让事务对象跨异步存活是bug 磁铁。读用state写通过editor.update开启新事务。4. Normalizers保留受限tx当前与目标一致normalizers: { node({ entry, next, tx }) { const value tx.value.get() tx.nodes.insert({ type: paragraph, children: [{ text: }] }) next() }, }规划明确保留受限 normalizertx。现有的负向类型测试已拒绝tx.normalize、tx.withoutNormalizing、tx.operations.replay以及整体值替换whole-value replacement。决策权衡为什么不是处处 state / 处处 tx / 处处 editor规划文档给出五条设计原则生命周期对象优先state表示读tx表示更新editor表示长生命周期运行时句柄同一事物不保留重复可读拼写不添加根级editor.state太容易被当成永远是最新的可变状态不在同步更新生命周期之外暴露tx不允许 Plate 形状的插件糖渗入 raw Slate。据此评估了四个备选方案处处只有editor—— 拒绝。示例变得冗长且隐藏了生命周期的合法性边界处处state 显式editor.update—— 拒绝。对 transform 中间件是错的因为 transform 本质是写钩子处处tx—— 拒绝。对 query 与 clipboard 生命周期是错的按生命周期区分上下文lifecycle-specific context——采纳。它贴合已有的state/tx划分并让异步边界保持诚实。在维护者反对账本中每一条裁决都记录了反对意见、替代方案与最终答复例如Query 中间件获得state反对者担心意外写出递归查询调用更容易了。答复被拦截查询的唯一延续是nextstate只用于相邻读取并补充测试。Transform 中间件获得tx反对者指出在 transform 真正运行于更新事务之前这不成立。答复先做分发重构state放进 transform 会教坏生命周期。Clipboard 获得state而非tx反对者认为粘贴处理器常立即写入tx更便捷。答复剪贴板是宿主入口读用state写另起editor.update便捷的同步粘贴换不来危险的异步粘贴。Operation 中间件不给tx保持 operation 中间件聚焦于 operation 分发未来仅在真实包有需要时再考虑专门的底层钩子。不提供根级editor.state保持editor.read作为一致的读边界中间件上下文才是便捷路径。生态参照Lexical、Tiptap、ProseMirror 怎么说规划在决策前完成了生态证据编译对应仓库 editor-architecture 研究源 中的三份文档Lexicallexical-read-update-extension-runtime.md命令与 transform 运行在更新上下文内读写有同步合法性边界。Slate 对齐点editor.read/editor.update、读回调拿state、更新回调拿tx。拒绝点Lexical 的 class 节点、$辅助函数与命令优先的应用 API。结论认同生命周期纪律公开风格分道扬镳。Tiptaptiptap-extension-command-react-dx.md扩展打包与命令目录让特性可发现。Slate 对齐点保留扩展打包与editor.api但让editor.update成为写生命周期。拒绝点不让命令成为默认变更 API。结论部分采纳。ProseMirrorprosemirror-transaction-view-dom-runtime.md命令接收当前state事务拥有文档、选区、marks、元数据与映射。Slate 对齐点query 中间件收statetransform 中间件收更新局部txoperation 中间件更贴近 operation 分发而非应用命令 DX。拒绝点ProseMirror 的插件复杂度、整数位置与命令优先的公开变更风格。结论认同事务所有权与命令-状态上下文公开扩展形状分道扬镳。规划还声明不引用外部资料替代仓库证据issue 语料库issue-intelligence-master-plan.md只是佐证需要替换执行/运行时模型而非 JSON 模型本规划不声称修复任何 issue。Issue 账目改 API 形状不声称修复规划明确不需要修改任何全局 issue 账本。相关行已归类在 gitcrawl-v2-sync-ledger.md、issue-coverage-matrix.md 与 fork-issue-dossier.md 中。该规划改变的是 API 目标形状并不证明某个 issue 的复现被修复。核心条目如下Issue聚类主张原因证明路线#3222plugin/API 设计Related扩展上下文清理回应插件作者压力但不关闭历史插件设计讨论plan/API proof only#4089高层插件 APIRelated规划保持 raw Slate 无立场给出扩展生命周期对象不添加产品级插件包plan/API proof only#4181自定义按键行为Not claimed该行已归类为可能无效transform 中间件覆盖 Slate 命令而非组件级按键特性请求no-claim#3177渲染组合Related扩展拥有渲染方向降低 prop 级组合压力但本规划不实现渲染器组合plan/API proof only#4721异步 Editable 事件Not claimedclipboard/transform/query 上下文未定义异步事件处理器返回语义no-claim#5233clipboard 片段格式Already fixed elsewhere规划保留 clipboard 边界方向不新增传输证明existing clipboard proof#4569insertData 文档Already fixed elsewhere规划改变未来回调上下文不涉及已认领的文档修复existing docs proof#1024clipboard schema 边界Relatedstate支持读检查MIME/文档类型仍是 DOM/model 传输问题plan/API proof only#2405命令作用域规范化Related受限 normalizertx保留命令级规则评估/性能仍是基准工作plan/API proof only#2288范围操作Related事务上下文方向与支持范围的 ops 一致但不新增 operation 暴露existing core proof plus plan#1770operation 组合Related保持 operation 中间件底层化避免假装 transform 上下文解决 operation 合并existing core proof plus plan#3874history 原子组Relatedtxtransform 上下文与事务感知 history 兼容但不认领 history API 闭合plan/API proof only#5080query 遍历Already fixed elsewherequery 中间件state不改变遍历顺序existing query proof#5684query 遍历歧义Not claimed该 issue 仍需具体复现给 query 中间件加state不是遍历修复no-claim类型测试要求用负向类型测试钉死契约规划要求在实施计划中新增或修订负向类型测试negative type tests这是让上下文契约真正可被编译器强制执行的机制query 中间件上下文暴露state、不暴露txquery 中间件只能调用一次next且不能用editor.update变更query generator 清理阶段依然不能变更transform 中间件上下文暴露tx示例不应为常规读取调用editor.readtransform 中间件tx同时包含读组与写组transform 中间件不暴露独立的state副本clipboard 上下文暴露state、不暴露txnormalizer 上下文保持受限tx无整体值替换、无递归 normalize、无 replayoperation 中间件上下文不暴露tx被禁用的扩展enabled: false从已安装扩展类型中抹除同名扩展取最新无replaces字段editor.getApi(extension)对已安装扩展返回类型化 API并拒绝未安装扩展。规划同时指定了精确的测试文件位置Slate v2 源码树内的测试契约如extension-methods-contract.ts、query-extension-contract.ts、generic-extension-namespace-contract.ts、generic-extension-install-contract.ts、normalization-contract.ts、clipboard-contract.ts以及 slate-dom 的clipboard-boundary.ts覆盖 transformtx上下文、事务路由、禁止双重next、同名取最新、enabled: falsetombstone、声明合并、禁用扩展类型擦除、getApi类型化与负向上下文断言等场景。示例更新要求用真实示例验证 DX规划要求实现后更新示例代码Slate v2 站点示例check-lists.tsx内联 transform 逻辑改用tx.selection.get、tx.nodes.find、tx.points.start、tx.nodes.set、tx.selection.settables.tsxdelete/backspace/enter 边界逻辑应落在deleteBackward、deleteForward、insertBreaktransform 中间件里而不是 keydown 事件分支images.tsxclipboard 读检查用state.selection.get同步写入用editor.update异步FileReader写入必须开启新的editor.update公开示例应优先使用扩展回调而不是renderElement/renderLeafprops扩展拥有渲染时rawEditable renderElement只作为底层逃生舱示例保留。在 Ralph Execution Ledger 中example-doc-sync阶段确认check-lists、tables、markdown-shortcuts、inlines、richtext扩展 transform 已改用txEditable 文档展示带tx的 transform 中间件surface contract 已更新以强制该形状。公开风险与回滚预案规划明确列出四项开放风险Transform 中间件拿不到tx直到分发路径被保证运行在更新事务内当前executeTransformMiddleware不强制implicitUpdateQuery 中间件拿到完整state后可能通过state递归调用同一查询——模型必须被文档化并测试next是当前查询的延续Clipboard 的state是快照时刻读取异步回调必须使用全新的editor.read或editor.update每个上下文都保留editor对editor.api有用但示例不得把根级 editor 变更教成常规路径。高风险预案High-Risk Deliberate Mode的 pre-mortem 覆盖三个灾难场景transformtx并非更新局部导致嵌套更新/陈旧读取querystate引发意外同查询递归与栈溢出clipboard 示例在异步文件读取期间误持事务对象。相应的证明矩阵要求单元测试证明deleteBackward、deleteForward、insertBreak、insertText、insertFragment及节点 transform 收到更新局部tx且默认next共享同一事务query 递归与变更拒绝测试clipboard 异步边界测试禁用扩展类型擦除与getApi负向测试示例使用内联生命周期对象公开表面测试拒绝旧methods、扩展commands、公开capabilities及与editor.api/state/tx竞争的辅助命名空间。回滚/硬切答案不提供兼容别名。若实现证明 transform 的tx中间件无法自洽则从 transform 中移除tx并暂时保留{ editor, next }绝不发布一个假的state妥协方案。实施阶段与验证门禁规划给出六个实施阶段Transform 分发让 transform 中间件在活动更新事务内执行并传入txQuery 上下文向 query 中间件传state测试递归/变更规则Clipboard 上下文传state写入保持editor.update更新异步示例类型表面为上下文可用性、禁用扩展、同名取最新、getApi添加负向类型测试示例用内联生命周期逻辑重写 checklists、tables、images、forced-layout硬切守卫让旧methods、commands、公开capabilities及Editor/DOMEditor/HistoryEditor辅助替代物远离公开文档与示例。验证门禁Verification Gates包括规划工件检查与实现期测试命令node tooling/scripts/completion-check.mjs实现期的 Ralph 运行门禁在 Slate v2 源码目录内执行bun test ./packages/slate/test/extension-methods-contract.ts ./packages/slate/test/query-extension-contract.ts ./packages/slate/test/generic-extension-namespace-contract.ts ./packages/slate/test/generic-extension-install-contract.ts ./packages/slate/test/normalization-contract.ts bun test ./packages/slate/test/clipboard-contract.ts ./packages/slate-dom/test/clipboard-boundary.ts bun --filter slate typecheck bun --filter slate-dom typecheck最终分数Final ScoreSlate-close DX 0.92、架构一致性 0.94、回归安全 0.89transform 路由是显式第一实施门禁、迁移主干 0.92、研究支撑 0.94、示例质量 0.91总体 0.93 ready。Ralph 执行账本随后记录了完整闭环tdd-passREDtx未定义导致测试失败GREEN活动更新视图穿入 transform 中间件、implementation-slice三个中间件上下文落地 负向类型测试、example-doc-sync示例与文档同步、final-gateslint、契约测试、slate/slate-dom/slate-react typecheck、站点 typecheck、vitest surface-contract、bun check全绿最终标记完成状态done。最终交接清单Final Handoff规划结尾的可执行清单也是插件作者理解 API 形态的速查表queries修订为{ state, editor, next, ...args }next仍是当前查询的延续transforms修订为{ tx, editor, next, ...args }第一步必须让 transform 中间件事务局部化normalizers保留受限txclipboard修订为{ state, editor, next }无txoperationMiddlewares保持({ editor, operation }, next)commitListeners保持(commit, snapshot)register保持{ editor, name, options, runtimeState, signal }api保持editor.api与editor.getApi(extension)根级state快捷方式砍掉Editor/DOMEditor/HistoryEditor公开替代物从目标公开路径砍掉Issue 账目不新增已修复 issue 声明所有相关行均已归类。延伸阅读本规划全文2026-05-18-slate-v2-extension-context-state-tx-coverage-ralplan.md生态研究源 lexical-read-update-extension-runtime.md、tiptap-extension-command-react-dx.md、prosemirror-transaction-view-dom-runtime.md设计决策记录slate-v2-state-tx-public-api-and-extension-namespaces.md、slate-v2-read-update-runtime-architecture.mdIssue 账目gitcrawl-v2-sync-ledger.md、issue-coverage-matrix.md、fork-issue-dossier.md、issue-intelligence-master-plan.md【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进