Plate Slate v2:restore-dom 家族闭包矩阵与删除遗留 DOM 恢复逻辑的判定方法
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本文围绕 Slate v2 迁移中对已删除的restore-dom家族的收尾工作展开,完整继承 restore-dom 家族闭包矩阵 的核心内容:将一段被删除的遗留代码拆解为可逐行判定(可证明、可裁断、或被工具链阻塞)的行为行,并给出每一行的分类结论与当前责任人。读完后你能掌握一套可复用的方法:当一个遗留守护逻辑(guard family)在新运行时架构中失去存在前提时,如何依据行为证据矩阵而非惯性将其“关闭”(close),而不是让它长期以模糊的幽灵状态悬而未决。
背景:restore-dom 是什么,以及它为什么曾经存在
在旧版(v1 时代)Slate React 运行时中,浏览器编辑输入会产生一类尴尬的时序问题:
- 浏览器在用户输入期间直接修改 DOM(插入文本节点、拆分/合并行内节点等);
- React 的重渲染(rerender)可能与这些浏览器侧的 DOM 变更发生竞争;
- 如果不干预,React 在下一次 commit 时可能把浏览器刚写入的内容“抹掉”或造成节点漂移。
为此,旧运行时专门设置了restore-dom家族,其职责是:在 React commit 落地之前,把被 React 移除/新增错位的 DOM 节点恢复回去,形成一个 commit 前的 DOM 修复(mutation replay)步骤。原始实现位于上游 Slate 仓库的以下两个文件(注意:这两个路径属于上游 legacy slate 仓库,不在当前 Plate 仓库内,此处仅作为来源说明):
packages/slate-react/src/components/restore-dom/restore-dom.tsxpackages/slate-react/src/components/restore-dom/restore-dom-manager.ts
该家族的行为特征可以归纳为四件相互耦合的事:
- 用户输入期间的变更缓冲:只在真正的用户输入期间缓冲浏览器对 DOM 的修改,其门控变量为
receivedUserInput; - commit 前恢复:通过 mutation observer 配合
getSnapshotBeforeUpdate生命周期,在 React commit 前恢复被移除/新增的 DOM 节点; - 组合输入(IME)敏感跳过:在 IME 组合输入(composition)期间显式跳过
characterData类变更的恢复,避免干扰正在进行的拼音/假名等组合过程; - Android 专属激活:整个 restore-dom 包装器仅在 Android 上激活(
IS_ANDROID守卫)。
这正是矩阵分析要处理的对象:这个家族不是“一个功能”,而是三种性质不同的防护机制的混合体。
任务定义:把“被删除的家族”拆成可证明的行为行
原始文档定义的目的一句话说清:把被删除的遗留restore-dom家族拆分成一行行可以被证明(proved)、被裁断(cut)、或被显式标记为工具链阻塞(tooling-blocked)的行为行(behavior rows)。
文档中标记的“当前责任人”(current owners)文件为:
packages/slate-react/src/components/editable.tsxpackages/slate-react/src/plugin/react-editor.tspackages/slate-dom/src/bridge.tspackages/slate-react/test/surface-contract.tsx
需要如实说明证据边界:从当前仓库结构看,上述slate-react/slate-dom组件文件并不包含在当前 checkout 中(当前仓库核心源码位于 packages/slate/src),原文档中的这些位置记录的是 v2 工作区的文件路径,本文只将其作为该闭包矩阵的责任划分记录转述,不作为本仓库中可点击的源码引用。本仓库内可以验证的锚点是:平台检测工具(含IS_ANDROID)仍以纯工具形式从 slate-dom 工具桥接层 导出——也就是说,Android 检测从“一个守护包装器的激活开关”退化成了“一个普通的运行时判断工具”。
文档同时声明,其历史批注性质(historical batch note)意味着:实时的闭包读数现在已折叠进 release-file-review-ledger.md。在该台账中,restore-dom.tsx的行确实留有记录(标记为adapted,说明为 React 19 落地了 null-aware 的 ref 类型而不改变 restore 时序),这与矩阵文档的“历史批注”定位相互印证。
行为矩阵:五条行为行及其分类结论
这是原文档的核心表格,完整继承如下(“Current read / 当前读数”与“owner / gap”列按原文语义整理):
| 遗留行为 | 遗留机制 | 当前运行时读数 | 分类 | 当前责任人 / 缺口 |
|---|---|---|---|---|
| 只在真正用户输入期间缓冲浏览器 DOM 变更 | receivedUserInput门控 | 当前运行时不再使用同一套 commit 前 DOM 重放生命周期 | justified-omission(有依据的省略) | 非当前生命周期的守护;见 restore-dom 是重渲染时代的守护而非 v2 当前运行时需求 |
| 在 React commit 前恢复被移除/新增的 DOM 节点 | mutation observer +getSnapshotBeforeUpdate | 当前运行时改为在input/compositionend上直接做 root 持有的 DOM commit,而非 commit 前 DOM 重放;可见行为行在当前接缝上均为绿(通过) | justified-omission | 被“当前 root 持有 DOM commit + 直接行为行”所取代 |
在组合输入期间忽略characterData变更恢复 | restore manager 中的显式跳过 | 主要 IME 行为行在当前承载行为的表面上为绿,且不依赖该家族 | covered-by-current-proof(已被当前证明覆盖) | 当前 IME 证明轨道(proof lanes)拥有此用户可见行为 |
| restore-dom 包装器仅 Android 激活 | IS_ANDROID守卫 | 当前 Android 行为行在 placeholder、无 FEFF placeholder、行内边缘、void 边缘、split/join 场景下均为绿,且不需要该包装器 | covered-by-current-proof | Android 证明的宽度窄于完整的遗留矩阵,但该守卫已不再作为独立未决家族存在 |
| 更新前恢复、更新后清除、然后恢复观察 | class 生命周期契约 | 当前运行时不再依赖这套重渲染时代的生命周期来维持编辑器可见行为的正确性 | justified-omission | 旧的生命周期形状不是当前架构契约 |
对这张矩阵,有三个读法要点:
- 分类词汇只有两类,且都有明确语义。
justified-omission表示“该机制所服务的生命周期在新架构中已经不存在,删除它有明确依据”;covered-by-current-proof表示“该机制守护的用户可见行为仍然重要,但已有当前运行时上的直接行为测试行覆盖它,因此旧机制可以关闭而行为责任不丢失”。矩阵里没有一行落入“未决/阻塞”状态,这正是该批注可以宣告收尾的前提。 - 每一行都必须能指认“谁现在拥有这个行为”。即使分类是
justified-omission,表格也要求写明行为去哪了(例如“被当前 root 持有 DOM commit 取代”),避免“删了代码但行为责任悬空”。 - 证据行(behavior rows)是判定单位,而不是文件。整个矩阵的组织原则就是:不以“restore-dom.tsx 这个文件删没删”为单位,而以“某一条用户可见行为有没有证明或替代”为单位。
硬读数:被删除的家族不是一个东西
原文档的 “Current Hard Read” 小节给出了整个收尾推理的支点:
被删除的家族不是“一个东西”,而是混合了三类机制——
- 仅 Android 的运行时防护
- 围绕 React commit 时机的 mutation 重放
- 对组合输入敏感的护栏
如果把它当作“一个功能”来讨论,就会陷入无意义的争论(要/不要一个整体)。拆成行为行之后,每一行都能独立给出上表中的分类。
同一小节还给出了当前证明栈(proof stack)已经说明的事实:
- 主要 IME 行在不依赖
restore-dom的情况下是绿的; - 瞬态 DOM 点焦点失败(transient DOM-point focus failure)已经 fail-closed(失败即收敛到安全行为,而不是静默漂移)。
由此得出的新读数:restore-dom曾经守护的那些承载行为的行,要么在当前表面上直接为绿,要么已不再是当前运行时生命周期所必需。
配合 restore-dom 是重渲染时代守护的解法文档,当前证明的具体范围可以进一步展开为五条直接证据行:
| 当前直接证明行 | 覆盖的旧守护场景 |
|---|---|
| 主要 IME 表面 | 组合输入敏感的characterData恢复跳过 |
| 焦点恢复 + 瞬态 DOM 点缺口 fail-closed | 焦点/DOM 点漂移期间的恢复行为 |
| Firefox 卸载后拖放清理 | 浏览器侧结构性 DOM 变更清理 |
| Firefox 表格多区间选择保持 | 选择状态在 DOM 变更下的保持 |
| Chromium 与 Android 上结构化 Enter/Backspace 的 split-join churn | 结构性浏览器 DOM 变更(现由编辑器自持的 keydown 路径覆盖) |
该解法文档明确:这些行正是“若新运行时仍依赖 mutation-replay 模型,restore-dom的缺失会显形为可见损坏”的地方——它们全绿,说明依赖已经真的不存在了。
当前运行时契约:Root 持有的 DOM Commit 取代 Commit 前重放
理解矩阵结论的关键,是理解 v2 的浏览器侧模型与旧模型的本质差异。对照原文档与其解法文档的描述:
旧模型(restore-dom 存在的前提)
浏览器修改 DOM → React 重渲染与之竞争 → 需要一个 commit 前的修复步骤把 DOM“放回去”,等 React 落地。整套receivedUserInput门控、getSnapshotBeforeUpdate快照、class 生命周期(更新前恢复、更新后清除、恢复观察)都服务于这条链路。
当前 v2 模型
编辑器 root 直接持有以下职责:
- 挂载(mount);
- 选择同步(selection sync);
- 在原生
input事件上把 DOM commit 回编辑器快照; - 在
compositionend事件上把 DOM commit 回编辑器快照。
也就是说,DOM 变化不再需要“在 React commit 前被修复”——编辑根节点自己就是 DOM→模型提交的所有者。这是矩阵中两条justified-omission(缓冲门控、class 生命周期契约)成立的根本原因:守护它们所针对的那条生命周期,在 v2 里已经不存在。
对解法文档给出的行为归口,可以总结为一个诚实的分割(honest split):
- 焦点期瞬态 DOM 点缺口:由当前 fail-closed 证明覆盖;
- 组合输入敏感的主要 IME 行为:由当前直接 IME 行覆盖;
- Enter/Backspace 的结构性浏览器变更:现由编辑器自持的 keydown 路径覆盖;
- 在 React commit 前重放任意浏览器/外部 DOM 变更:不再默认为当前运行时的头等契约。
闭包读数:两种关闭方式
原文档的 “Closure Read” 给出该家族的最终收尾形态,它实际上给出了关闭一个遗留家族仅有的两种合法方式:
- 行为行在当前证明表面上已被覆盖(
covered-by-current-proof)——对应 IME 行为行与 Android 行为行; - 重渲染时代的 mutation 重放生命周期被显式标记为“非当前”(
justified-omission)——对应receivedUserInput缓冲门控与 class 生命周期契约。
注意第二种方式的关键词是“显式”(explicitly):不证明它“错了”,而是证明它服务的生命周期不再是当前架构契约。这避免了两种常见的坏收尾:要么永远留着一个没人敢删的幽灵家族,要么在没有任何分类记录的情况下悄悄删掉。
可复用规则:判定遗留守护是否该关闭
该闭包矩阵配套沉淀了一条可复用规则(引自 解法文档),适用于任何“旧守护修复浏览器 DOM 漂移 vs 新运行时”的裁断:
当一个遗留守护(legacy guard)存在的目的是在 React commit 前修复浏览器 DOM 漂移时,先问:新运行时是否还有同样的生命周期?
如果新运行时直接把 DOM commit 回模型,且承载行为的可见行为行为行为行为行为行都是绿的,就不要因为惯性而把旧守护家族一直开着。
正确动作是:直接关闭承载行为的行为行,并把旧的、生命周期特定的修复机器标记为“非当前守护”(non-current guard),而不是把它当成不朽的阻塞项(immortal blocker)。
用一句话概括其方法论内核:守护的存废由“它服务的生命周期是否还存在”决定,行为的存废由“当前证明行是否绿”决定——两者都要显式记录,缺一不可。
相关文档索引
- restore-dom 家族闭包矩阵(本文主体源文档)
- restore-dom 是重渲染时代守护而非 v2 当前运行时需求(配套解法记录)
- release-file-review-ledger.md(当前实时的闭包读数已折叠至此)
- 删除闭包协议 rollout(闭包分类术语的使用语境)
- slate-dom 工具导出(含 IS_ANDROID 等平台检测的当前仓库锚点)
适用前提说明:本文所述的“行为行为行绿/通过”均为源文档在其记录时点对证明栈的读数转述,属于该团队维护的文档事实,不代表对任意检出版本的实时断言;原文档中的 legacy 源码路径指向上游 Slate 仓库,current owners文件路径指向 v2 工作区,二者均不在本仓库可点击范围内,引用时请以其在源文档中的原始定位为准。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考