面试防线:Tiptap 原子化胶囊如何防止光标越界或被部分删除

面试官提问
“你提到业务变量在编辑器里是带有背景色的胶囊(Node View)。但如果用户强行通过方向键把光标移动到胶囊文字内部去修改,或者按退格键只删除了胶囊一半的字,你怎么保证底层绑定的变量数据结构不被破坏?”

核心回答思路 (STAR法则)

  1. 业务痛点 (Situation)
    如果只是用简单的 HTML/CSS 把文字加个背景色,对于底层数据来说它仍然是一段松散的文本。一旦被用户随意修改、拆分,在最后向后端提交 schema 时,我们原本想要提交的 { type: "variable", id: "field_123" } 结构就会被破坏成毫无意义的脏数据。

  2. 技术考量 (Task)
    我们必须在编辑器模型(Model)层,让这个变量变成一个真正不可拆分、光标无法侵入的原子(Atom)节点。这就是我放弃直接用 Contenteditable 原生开发,转而选型 ProseMirror/Tiptap 的根本原因。

  3. 架构决策 (Action)

  • Schema 定义 Atom 节点:在定义 Tiptap 的自定义 Extension 时,我将这个 Variable 节点的 Schema 配置为 atom: trueinline: true。这告诉底层的 ProseMirror 文档树:这个节点就像一个 <img> 标签一样,是一个不可拆分的单一实体,它没有内部内容,光标只能在它的左边或右边,绝不能进入它内部。
  • 自定义 NodeView 屏蔽原生事件:我编写了 React NodeView 来渲染这个胶囊的 UI。在渲染的 DOM 外层,我设置了 contenteditable="false" 和特定的 user-select 属性。这从浏览器原生交互层彻底切断了用户想通过鼠标双击选中文本或者拖拽修改内部文字的企图。
  • 自定义 Backspace 快捷键拦截:我还在该扩展中注册了键盘事件的拦截器。当检测到光标在这个原子节点后面且按下了 Backspace 时,触发一次特殊的 transaction,直接将这个节点作为整体从文档树中 deleteRange 删除,提供极佳的类似于 Excel 的操作手感。
  1. 业务价值 (Result)
    通过从底层 Schema 到表层 DOM 事件的全面封锁,我们彻底杜绝了公式输入中的脏数据产生。业务用户在面对复杂公式时再也不会因为误触而毁掉整个表达式,不仅零漏洞,而且做到了让非技术人员极度舒适的零学习成本交互。