基于Mutation Observer监听Slate编辑器动态节点字符数据的问题
解决Slate编辑器自定义节点的输入/删除逻辑问题
别用Mutation Observer了,直接基于Slate的编辑器状态监听和官方API实现需求更靠谱——毕竟Slate的DOM是内部状态驱动的,DOM监听很容易错过或误判更新。
核心实现思路
- 用Slate的
onChange事件监听编辑器内容变化,直接操作编辑器状态而非DOM - 通过
TransformsAPI修改节点内容,处理「输入第一个字符替换零宽span」「删除最后一个字符恢复零宽结构」的逻辑 - 在
Editable的onKeyDown事件中监听退格键,处理「删除整个div节点」的逻辑
代码实现(React环境)
1. 完整编辑器组件代码
import { Slate, Editable, Transforms, Editor, Range } from 'slate-react'; import { createEditor } from 'slate'; const CustomSlateEditor = () => { const editor = createEditor(); // 初始内容:包含自定义div节点(对应data-slate-node="element") const initialValue = [ { type: 'custom-block', data: { 'data-slate-node': 'element' }, children: [ { type: 'zero-width-span', data: { 'data-slate-zero-width': 'n', 'data-slate-length': 0 }, text: '\uFEFF<br>' // 存储实际零宽字符,而非HTML实体 } ] } ]; const handleEditorChange = (value) => { value.forEach((node, nodeIndex) => { // 匹配自定义div节点 if (node.type === 'custom-block' && node.data?.['data-slate-node'] === 'element') { const [childNode] = node.children; // 场景1:输入第一个字符,替换零宽span为文本span if (childNode.type === 'zero-width-span' && childNode.text !== '\uFEFF<br>') { const inputText = childNode.text.replace('\uFEFF<br>', ''); if (inputText) { Transforms.setNodes( editor, { type: 'text-span', data: { 'data-slate-string': true }, text: inputText }, { at: [nodeIndex, 0] } ); } } // 场景2:删除最后一个字符,恢复零宽span结构 if (childNode.type === 'text-span' && childNode.text === '') { Transforms.setNodes( editor, { type: 'zero-width-span', data: { 'data-slate-zero-width': 'n', 'data-slate-length': 0 }, text: '\uFEFF<br>' }, { at: [nodeIndex, 0] } ); } } }); }; // 场景3:退格键删除整个div节点 const handleKeyDown = (e) => { if (e.key !== 'Backspace') return; const selection = editor.selection; if (!selection || !Range.isCollapsed(selection)) return; const [currentNode, path] = Editor.node(editor, selection.anchor.path); const parentPath = path.slice(0, -1); const [parentNode] = Editor.node(editor, parentPath); // 确认当前选中零宽span,且父节点是自定义div if (currentNode.type === 'zero-width-span' && parentNode.type === 'custom-block') { e.preventDefault(); Transforms.removeNodes(editor, { at: parentPath }); } }; return ( <Slate editor={editor} initialValue={initialValue} onChange={handleEditorChange}> <Editable onKeyDown={handleKeyDown} /> </Slate> ); }; export default CustomSlateEditor;
2. 关键细节说明
- 节点类型匹配:代码里的
custom-block和zero-width-span是自定义节点类型,需要根据你实际的Slate schema调整,确保和编辑器生成的DOM节点对应 - 零宽字符处理:Slate内部存储的是实际字符
\uFEFF,不是HTML实体,直接操作文本内容即可,不用处理实体转换 - 状态同步:所有节点修改都通过Slate官方API完成,确保编辑器状态和DOM完全同步,不会出现状态不一致的问题
为什么放弃Mutation Observer?
Slate用虚拟DOM机制,DOM更新是批量且内部驱动的,Mutation Observer很难精准捕获leaf节点的字符变化(比如输入字符时的DOM更新可能被合并),而直接监听编辑器状态是Slate官方推荐的方式,能精准控制节点修改逻辑。
内容的提问来源于stack exchange,提问作者optimus
相关产品推荐
相关产品推荐

