Slate v2 访问器与事务公共 API 恢复:getChildren / setChildren / withTransaction / applyBatch 波次计划落地解析
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
导读
本文围绕 plate 仓库中packages/slate的 Slate v2 演进计划 2026-04-18-slate-v2-slate-accessor-batch-wave-plan.md,系统讲解 Slate v2 如何恢复"访问器 + 快照 + 事务"这一整组最高杠杆的公共 API 接缝:getChildren/setChildren、Editor.getSnapshot/replace/reset/subscribe、Editor.withTransaction以及Transforms.applyBatch。读完本文,你将理解这组 API 为什么是"source-first 审计"中被选中的第一波恢复对象、公共接口与运行时接缝分别落在哪些源码文件、测试如何以 TDD 方式拉取草稿(draft)语义、哪些 legacy 语义被显式保留为 cut,以及如何在本地复现验证。
一、背景:为什么这一波聚焦 accessor / transaction 接缝
Slate v2 的恢复工作遵循"source-first"原则:先审计当前收窄(narrowed)后的公共 API 与 legacy Slate 及真实草稿之间的差距,再按杠杆高低分批恢复。此前对Editor.before(...)、Editor.after(...)、Editor.positions(...)的源码级审计发现,这些运行时文件已经与 legacy 处于"同路径接近"(same-path close)状态,不是最值得投入代码的地方。
与之形成对比的是,accessor / transaction 表面仍然是一处真实存在的已发布收窄(real shipped narrowing):
| 收窄点 | 现状 |
|---|---|
getChildren/setChildren | 当前slate-v2已丢失 |
| 快照 / 监听接缝 | 当前slate-v2没有公开的 snapshot / listener 接口 |
| 事务接缝 | 当前slate-v2没有公开的 transaction 接口 |
Transforms.applyBatch(...) | 当前slate-v2缺失 |
| 真实草稿(draft) | 仍然暴露getChildren、setChildren、Editor.withTransaction(...)、Transforms.applyBatch(...) |
也就是说,草稿源仍然保留着这组接缝,而当前收窄表面却把它们删掉了。按照该计划的硬性规则:不能让当前收窄表面默认获胜(do not let the current narrowed surface win by default),也不要假装草稿保留了它实际删除的接缝(do not pretend the draft kept seams it actually deleted)。恢复的优先级是:先恢复公共 API 形态(recover the public API shape first),同时保持此前已裁掉的失败语义继续 cut,除非被显式重新打开。
这一方法论在 slate-editor-api.md 中有延续记录:tranche: 3、规则是"先恢复导出的 editor 契约,再修复消费者"(recover exported editor contracts before fixing consumers),并明确"当前收窄不再默认被视为坏的——如果更好的 API 更干净,旧表面是负担,就显式 cut 或降级"。
二、恢复目标全景:八条公共接缝
计划在 Goal 中列出本波次要恢复的完整目标清单:
getChildrensetChildrenEditor.getSnapshot(...)Editor.replace(...)Editor.reset(...)Editor.subscribe(...)Editor.withTransaction(...)Transforms.applyBatch(...)
其中getChildren/setChildren属于访问器(accessor)接缝,getSnapshot/subscribe/replace/reset属于快照与外部替换接缝,withTransaction与applyBatch属于事务接缝。三者共同构成一个完整的"读(read)→ 事务写(update)→ 一次提交发布(commit publish)"的公共生命周期。在 architecture-contract.md 中,这一形态被概括为:
editor.read(() => { editor.getSelection(); editor.getChildren(); }); editor.update(() => { editor.insertNodes(node); editor.setNodes({ color: "orange" }, { at: [0] }); editor.moveNodes({ at: [3], to: [1] }); });规则是:editor.update创建事务边界,editor.read读取一致的已提交状态,基本 editor 方法作用于活动事务,提交订阅者(commit subscribers)是主要的提交后边界,而不是用editor.onChange替代作为扩展面。也就是说,即使开发者写的是单操作代码,原生模型也默认是事务性的——这正是withTransaction与applyBatch存在的意义。
三、公共接口层:interfaces/editor.ts 中恢复的签名
计划中"Landed Implementation / Public interface"一节说明,本波次在interfaces/editor.ts中恢复:
getChildrensetChildrengetSnapshotreplaceresetsubscribewithTransaction- snapshot 相关类型(snapshot types)
在本仓库中,packages/slate的公共接口类型定义位于 editor-api.ts,其EditorApi<V>类型以三组交叉类型组织:查询类(above/before/after/nodes/positions/string等)、DOM 桥接类(toDOMNode/toSlatePoint/findEventRange等)、扩展类(block/blocks/isAt/some/scrollIntoView等)。访问器与事务方法的恢复即是在这条公共类型链路上新增成员,让Editor.getChildren(editor)、Editor.withTransaction(editor, tx => ...)具备显式类型签名。
四、运行时接缝:轻量公共状态 + 可覆写实例方法
计划强调,恢复不采用"整包引入草稿的批量(batching)子系统"的激进路线,而是在core/public-state.ts中新增轻量级公共事务 / 访问器状态(lightweight public transaction/accessor state),然后在create-editor.ts中恢复:
- 公共访问器方法(public accessor methods)
- 可枚举的
children访问器,路由到这些方法(enumerablechildrenaccessor routed through those methods) - 快照 / 监听方法(snapshot/listener methods)
- 事务方法(transaction methods)
在本仓库中,编辑器实例的组装逻辑位于 create-editor.ts:它基于slate的createSlateEditor()创建底层编辑器,通过多组Object.assign用bindFirst绑定各内部实现(deleteBackward、insertText、setNodes、splitNodes、withoutNormalizing等),并组装editor.api与editor.transforms(editor.tf)两组命名空间。访问器 / 快照 / 事务方法的恢复,就是在这一组装点注入对应实现,从而保证:
editor.children读操作最终路由到getChildren/setChildren;Editor.setChildren(...)路由到可覆写的实例方法(overrideable instance method)。
随后在interfaces/transforms/general.ts中恢复Transforms.applyBatch(...)——计划明确它只是Editor.withTransaction(...)之上的"薄语法糖"(thin sugar)。同时更新core/apply.ts,使其在活动事务内抑制普通的 flush 调度,并在提交时只发布一次(suppress ordinary flush scheduling inside an active transaction and publish once at commit)。这一"事务内不逐操作发布、提交时统一发布"的行为,是整条接缝的性能与一致性关键:一次事务提交对应一次快照发布,而不是一串可观察的部分变更。
这些落地状态在 slate-editor-api.md 中被进一步固化为一套明确的读写层次:
- 读层次:
Editor.getOperations(editor)是规范的 operations 读接缝;Editor.withTransaction(editor, tx => ...)暴露草稿式读(tx.children、tx.selection、tx.marks、tx.operations);tx.apply(op)是第一个显式的事务私有写接缝;applyOperation(editor, op)是内部 helper/transform 写接缝。 - 写层次:
Editor.apply(editor, op)成为事务接缝之上的显式公共单操作写入者;editor.update是写边界;直接替换apply/onChange不再是扩展点。 - 兼容性镜像:
editor.children、editor.selection、editor.marks、editor.operations被重新归类为兼容性镜像(compatibility mirror),而非主读取接缝;内部热路径源码(如delete-text.ts、get-default-insert-location.ts)改用Editor.getChildren(editor)读取实时草稿状态;slate-reactprovider 回调改读Editor.getChildren/Editor.getOperations/Editor.getLiveSelection。 - 提交次序:提交订阅者在
editor.onChange()之前触发,因此onChange更诚实地被理解为"快照存储接缝之上的 legacy 兼容回调"。
五、TDD 姿态:先 RED 拉取草稿语义,再恢复最小接缝
计划明确采用 TDD 执行形态:
- 先拉取聚焦的、草稿背书的测试为RED;
- 保持当前相关测试green;
- 恢复让这些测试通过所需的最小公共接缝;
- 聚焦的 RED 集合变绿后,才进行重构。
优先拉取的草稿背书 RED 切片包括:
surface-contract.ts中的访问器路由(accessor routing)transaction-contract.ts中的事务可见性与退出时发布(transaction visibility and publish-on-exit)transaction-contract.ts中的抛错回滚(rollback-on-throw)applyBatch(...)与手写withTransaction(...)的对等性(parity)
同时保持既有测试存活,如packages/slate/test/index.spec.ts。
新增的聚焦公共接缝测试集中在test/accessor-transaction.test.ts,覆盖五类行为:
| 测试点 | 验证内容 |
|---|---|
| children 访问器 | 读取路由经过getChildren/setChildren |
Editor.setChildren(...) | 路由经过可覆写的实例方法 |
Editor.withTransaction(...) | 替换状态在事务内可见,退出时只发布一次 |
Transforms.applyBatch(...) | 与手写Editor.withTransaction(...)等价,覆盖:重复精确路径set_node、混合 text/selection/node 操作、结构性 insert/move/set 批量 |
withTransaction(...)抛错 | 对暂存变更执行回滚(rolls back staged changes on throw) |
此外,本波次还恢复了 108 个同路径 legacy JSX fixture 文件(packages/slate/test/**),目的是让现有index.spec.ts测试框架"重新诚实"——此前它因 fixture 导入损坏而失败,恢复后不再出现误导性的失败。
在 master-roadmap.md 的 Tranche 3 落地清单中,accessor-transaction.test.ts被列为"当前直接属主且为绿"(present and green)的证明文件之一,与query-contract.ts、operations-contract.ts、legacy-editor-nodes-fixtures.ts、legacy-interfaces-fixtures.ts、legacy-transforms-fixtures.ts并列;同时 roadmap 也给出重要更正——独立的snapshot-contract.ts是宽泛的 oracle 属主,默认不纳入 package-closeout 证明,bun test ./packages/slate/test变绿不代表snapshot-contract.ts独立变绿,这类细节体现了该仓库对证明边界的严谨态度。
六、验证命令
计划给出的完整验证序列如下(路径以计划中的本地 checkout 为准,在本仓库中对应packages/slate包):
# 安装依赖 cd <slate-v2-checkout> && bun install # 构建 slate 包 cd <slate-v2-checkout> && bunx turbo build --filter=./packages/slate # 类型检查 cd <slate-v2-checkout> && bunx turbo typecheck --filter=./packages/slate # 修复 lint cd <slate-v2-checkout> && bun run lint:fix # 运行新增的访问器/事务聚焦测试 cd <slate-v2-checkout>/packages/slate && bun test ./test/accessor-transaction.test.ts # 运行既有完整测试框架,确认无回归 cd <slate-v2-checkout>/packages/slate && bun test ./test/index.spec.ts对应到本仓库,包级收尾检查在 slate-editor-api.md 中被记录为以下四条全部为绿:bun test ./packages/slate/test、bunx turbo build --filter=./packages/slate、bunx turbo typecheck --filter=./packages/slate、bun run lint:fix(外加bun run lint)。这些命令共同构成了该波次的"诚实回归防线":新增 RED 测试绿、既有测试不破、类型与 lint 干净。
七、显式边界:withBatch 不声称恢复
计划在 "Explicit Boundary" 一节划定了非常清晰的边界:
- 真实草稿的事务接缝是
Editor.withTransaction(...); Editor.withBatch(...)不在真实草稿的 source of truth 中;- 因此本波次不声称恢复了
withBatch(...)——如果未来想要这个 legacy 兼容名称,应作为一次单独的显式决策处理; - 精确的 legacy "抛错时部分提交"(partial-commit-on-throw)批量语义继续保持 cut。
这与 slate-editor-api.md 中的 Tranche 3 规则完全一致:"恢复的公共名称不会自动恢复它们背后的每一个更深层 legacy 语义,显式 cut 要保持显式"(Recovered public names do not automatically recover every deeper legacy semantic behind them. Keep explicit cuts explicit)。也就是说,"名字恢复"与"语义恢复"是两件事,本波次只对名字和契约负责,深层失败语义的取舍留待后续独立决策。
八、结果与下一步
计划在 Result 中给出的结论是:草稿背书的 accessor / transaction / applyBatch 波次已全部为绿(The draft-backed accessor/transaction/applyBatch wave is green)。同时给出一个方法论层面的建议:下一个 tranche-3 动作应当通过重新阅读 ledger 对照这块已落地的接缝来选择,而不是复用该波次之前过时的队列(stale pre-wave queue)。
结合 CHANGELOG.md 中记录的 API 迁移方向,可以看到这组接缝在更广的 API 版图里的位置:getChildren([node, path])迁移为Array.from(NodeApi.children(editor, path)),大量 legacy 函数(getNodeChildren、getFirstChild、getLastChild等)统一收敛到NodeApi.*命名空间,而getSnapshot/subscribe/withTransaction/applyBatch则承担起"快照存储 + 事务写 + 一次提交"这一新的运行时契约。这正是 architecture-contract.md 所强调的:slate必须拥有getSnapshot(editor)、subscribe(editor, listener)以及一个显式的外部值 / 快照替换入口,而slate-react不应通过外部观察可变编辑器状态来自造 store,受控模式也不应以"prop 变了跑 effect 再推回 core"来实现。
小结
本波次的价值不在于"多恢复了几个方法名",而在于把 Slate v2 的读 / 写 / 观察模型从"可变对象的直接属性操作"推进到"访问器 + 快照 + 事务 + 一次提交发布"的显式契约:getChildren/setChildren收紧了 children 的读写路径,getSnapshot/subscribe/replace/reset提供了外部 store 式观察与受控替换入口,withTransaction与applyBatch把多操作批处理变成了一等公民,且严格遵循"事务内不逐操作 flush、退出时发布一次、抛错则回滚"的语义。同时,通过withBatch边界与 partial-commit-on-throw 语义的显式 cut,它示范了在 API 恢复工作中如何区分"契约恢复"与"语义恢复",避免把未经验证的 legacy 行为悄悄带回来。
对本仓库感兴趣的读者,可以继续阅读以下路径深入:
- 计划原文:docs/plans/2026-04-18-slate-v2-slate-accessor-batch-wave-plan.md
- 编辑器 API 台账:docs/slate-v2/ledgers/slate-editor-api.md
- Tranche 3 路线图落地状态:docs/slate-v2/master-roadmap.md
- 架构契约(read/update 生命周期、快照存储、提交订阅者):docs/slate-v2/references/architecture-contract.md
- 公共接口类型定义:packages/slate/src/interfaces/editor/editor-api.ts
- 编辑器实例组装:packages/slate/src/create-editor.ts
- 迁移映射与命名空间收敛:packages/slate/CHANGELOG.md
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考