draft-js保存恢复时inlineStyleRanges顺序错误致编辑器状态异常
首先纠正一个常见误区:Draft.js 的convertFromRaw方法本身不要求inlineStyleRanges数组按任何顺序排列。不管数组里的样式范围是按偏移量正序、倒序还是乱序存放,只要每个范围项的offset、length、style三个属性值准确,解析生成的ContentState就不会出错。你观察到的数组索引错位,本身根本不会导致编辑器恢复异常。
真正的问题出在你的内联样式更新逻辑上:你现在先调用removeInlineStyle再调用applyInlineStyle的写法,在选区跨多个已经带有不同内联样式的文本段时,会生成样式范围重叠、边界计算异常的ContentState。平时你直接在编辑器里输入操作时,Draft内置的实时交互兜底逻辑(包括选区合并、事务原子化处理)会把这些异常修正,所以看起来功能正常;但序列化存库再反序列化的时候,没有了实时交互层的兜底,这些计算错误就会直接暴露出来。
另外你在EditorState.push之前提前调用acceptSelection修改选区的操作也是错的,这会导致Draft内部做样式范围映射时,锚点位置和实际内容不匹配,进一步放大样式计算的错误。
优先用Draft内置的RichUtils工具方法处理内联样式切换,它内部已经封装好了跨样式块的边界计算、范围去重、选区同步逻辑,不会出现计算错误:
const newEditorState = RichUtils.toggleInlineStyle( editorState, inlineStyle ); setEditorState(newEditorState);
如果你有自定义逻辑必须手动用Modifier实现,不要同时调用remove和apply方法——先判断当前选区是否已经包含目标样式,再决定是移除还是新增,同时不要在push状态之前提前修改选区:
// 先判断当前选区是否已存在目标内联样式 const currentStyle = editorState.getCurrentInlineStyle(); let nextContent; if (currentStyle.has(inlineStyle)) { nextContent = Modifier.removeInlineStyle( editorState.getCurrentContent(), newSelection, inlineStyle ); } else { nextContent = Modifier.applyInlineStyle( editorState.getCurrentContent(), newSelection, inlineStyle ); } // push状态时直接传入原editorState,不要提前改selection let newEditorState = EditorState.push( editorState, nextContent, 'change-inline-style' ); // 如果需要强制同步选区,在push完成之后再调用acceptSelection newEditorState = EditorState.acceptSelection(newEditorState, newSelection); setEditorState(newEditorState);
改完逻辑后如果还存在恢复异常,优先检查存库/读库环节的问题:
- 确认序列化后的JSON字符串存库时没有被截断、转义错误(比如引号、反斜杠被自动转义导致JSON.parse后属性值异常)
- 不要手动对
convertToRaw返回的inlineStyleRanges数组做排序、过滤操作,原生输出的结构哪怕顺序乱也是可被正确解析的 - 如果用了自定义的样式装饰器(decorator),确认装饰器的匹配逻辑和内联样式范围没有冲突
内容的提问来源于stack exchange,提问作者Junre Zapico

