)
Draft.js AtomicBlockUtils 完全指南在编辑器中插入与移动原子块Atomic Block【免费下载链接】draft-jsA React framework for building text editors.项目地址: https://gitcode.com/gh_mirrors/dr/draft-jsAtomicBlockUtils是 Draft.js 提供的静态工具模块专门用于编辑原子块atomic block——即type: atomic、文本内容不可直接编辑、通常承载图片/音视频/嵌入内容等富媒体对象的块级内容。本篇指南以官方 API 文档docs/APIReference-AtomicBlockUtils.md为骨架结合仓库源码src/model/modifier/AtomicBlockUtils.js与测试用例深入解析其实现原理带你掌握如何在编辑器中选择区块插入、替换或移动原子块并理解其内部的数据流转机制。模块概览纯函数式设计的静态工具集AtomicBlockUtils是一组静态工具函数用于原子块编辑。它的设计遵循 Draft.js 一贯的不可变数据流原则每个方法都接收EditorState对象及相关参数每个方法都返回一个新的EditorState对象原状态不会被修改方法内部通过DraftModifier、moveBlockInContentState等底层工具组合出完整的编辑操作。在 src/Draft.js 中AtomicBlockUtils与EditorState、RichUtils、Modifier等一起被导出开发者可以直接通过import {AtomicBlockUtils} from draft-js使用。模块仅暴露两个静态方法方法作用insertAtomicBlock(editorState, entityKey, character)在当前选区位置插入一个原子块moveAtomicBlock(editorState, atomicBlock, targetRange, insertionMode?)将已有原子块移动到目标位置insertAtomicBlock()插入原子块方法签名insertAtomicBlock: function( editorState: EditorState, entityKey: string, character: string ): EditorState三个参数的职责参数类型说明editorStateEditorState当前编辑器状态包含内容与选区entityKeystring要附加到原子块上的实体键Entity Key由contentState.createEntity()创建characterstring填充原子块的占位字符实践中几乎总是传入单个空格 不能传空字符串占位字符为什么必须是空格在 examples/draft-0-10-0/media/media.html 的媒体编辑器示例中源码注释明确警告The third parameter here is a space string, not an empty string. If you set an empty string, you will get an error:Unknown DraftEntity key: null原因可以从实现中找到源码在构建原子块记录时执行了List(Repeat(charData, character.length))src/model/modifier/AtomicBlockUtils.js。若character为空字符串character.length为 0characterList将是一个空列表Draft.js 便无法为原子块关联实体进而抛出Unknown DraftEntity key: null。因此请始终传入 。完整调用范例参照 examples/draft-0-10-0/tex/js/modifiers/insertTeXBlock.jsTeX 公式示例与 media 示例标准的插入流程分三步创建实体 → 把实体写回EditorState→ 调用insertAtomicBlockimport {AtomicBlockUtils, EditorState} from draft-js; function insertImage(editorState, src) { // 1. 在 ContentState 上创建实体获得 entityKey const contentState editorState.getCurrentContent(); const contentStateWithEntity contentState.createEntity( image, // 实体类型自定义字符串即可 IMMUTABLE, // 可变性MUTABLE / IMMUTABLE / SEGMENTED {src}, // 实体数据例如图片地址 ); const entityKey contentStateWithEntity.getLastCreatedEntityKey(); // 2. 用含实体的 ContentState 重建 EditorState const newEditorState EditorState.set(editorState, { currentContent: contentStateWithEntity, }); // 3. 插入原子块character 必须是空格 return AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ); }底层实现四步管线源码 src/model/modifier/AtomicBlockUtils.js 展示了插入操作的完整数据管线全部基于DraftModifier的不可变操作组合而成删除选中范围DraftModifier.removeRange(contentState, selectionState, backward)先清除当前选区内容保证插入点是干净的切分目标块DraftModifier.splitBlock(afterRemoval, targetSelection)把选区所在的块一分为二为原子块腾出独立位置设置块类型DraftModifier.setBlockType(afterSplit, insertionTarget, atomic)将插入目标块标记为atomicatomic是 src/model/constants/DraftBlockType.js 中列出的核心块类型之一替换为片段构造包含两个新块的 BlockMap 片段用DraftModifier.replaceWithFragment写入内容。值得注意的是插入的不只是原子块本身还会紧随其后创建一个type: unstyled的空白分隔块源码第 71-90 行的atomicDividerBlockConfig。这个分隔块的用途是原子块本身editable: false不可聚焦用户在原子块之后需要一个普通文本块来放置光标继续输入。若启用了实验性的树形数据支持gkx(draft_tree_data_support)即使用ContentBlockNode时两个块之间还会通过nextSibling/prevSibling建立兄弟链接源码第 76-85 行。原子块本身的结构为源码第 64-69 行{ key: generateRandomKey(), type: atomic, text: character, // 通常是单个空格 characterList: List(Repeat(charData, character.length)), }其中charData CharacterMetadata.create({entity: entityKey})即原子块文本的每个字符都携带同一个实体引用渲染层通过block.getEntityAt(0)即可取回实体。最后新内容通过EditorState.push(editorState, newContent, insert-fragment)入栈变更类型为insert-fragment且selectionAfter被显式设置为hasFocus: true保证插入后焦点与光标状态正确。moveAtomicBlock()移动原子块方法签名moveAtomicBlock: function( editorState: EditorState, atomicBlock: ContentBlock, targetRange: SelectionState, insertionMode?: DraftInsertionType ): EditorState参数说明参数类型说明editorStateEditorState当前编辑器状态atomicBlockContentBlock即BlockNodeRecord要被移动的原子块对象targetRangeSelectionState目标位置选区insertionModeDraftInsertionType可选replace默认/before/afterDraftInsertionType定义于 src/model/constants/DraftInsertionType.jsexport type DraftInsertionType replace | before | after;三种模式语义模式行为before把原子块插到目标块之前依据targetRange.getStartKey()定位after把原子块插到目标块之后依据targetRange.getEndKey()定位replace默认省略参数时用原子块替换目标选区范围内的内容实现逻辑三条分支路径源码 src/model/modifier/AtomicBlockUtils.js 将移动操作划分为两条主路径路径一显式before/after模式直接定位目标块before取targetRange.getStartKey()对应的块after取getEndKey()对应的块然后调用moveBlockInContentState(contentState, atomicBlock, targetBlock, insertionMode)完成移动。路径二replace模式默认这是更复杂的路径需要结合目标选区的形态决定最终插入方式先用DraftModifier.removeRange删除目标选区内容得到选区selectionAfterRemoval若getStartOffset() 0目标块头部无残留文本直接移动原子块到该块之前before否则若getEndOffset() targetBlock.getLength()目标块尾部无残留文本移动到该块之后after否则目标选区位处块中间先用DraftModifier.splitBlock把目标块切开再将原子块移动到切分点之前。移动完成后同样以EditorState.push(editorState, newContent, move-block)入栈selectionBefore记录原选区、selectionAfter设置hasFocus: true。底层依赖moveBlockInContentStatemoveAtomicBlock的核心移动动作由 src/model/transaction/moveBlockInContentState.js 完成。该函数有两个关键约束由invariant强校验invariant(insertionMode ! replace, Replacing blocks is not supported.)——moveBlockInContentState本身不支持replace模式replace 语义在moveAtomicBlock上层已被先删后插化解invariant(blockKey ! targetKey, Block cannot be moved next to itself.)——不允许把原子块移动到它自己旁边。后者在测试 src/model/modifier/tests/AtomicBlockUtils-test.js 中被大量覆盖包含 8 种移动到自身附近的非法场景上移到前块之后、下移到后块之前、replace 自身等全部断言抛出Block cannot be moved next to itself.。另外当被移动块是ContentBlockNode实验性树形数据时函数会通过updateBlockMapLinks同步维护parent/prevSibling/nextSibling/children等树结构指针源码第 45-138 行并借助getNextDelimiterBlockKeysrc/model/transaction/exploration/getNextDelimiterBlockKey.js找到分隔块将原子块连同其后续兄弟一并搬移。测试覆盖理解各种选区形态的行为src/model/modifier/tests/AtomicBlockUtils-test.js 完整验证了该模块在各种选区形态下的行为是学习边界条件的最佳素材insertAtomicBlock 场景折叠选区collapsed selection位于块首 / 块中 / 块尾非折叠选区位于块首 / 块中 / 块尾插入即替换选中文本跨块选区cross-block selection启用实验性树形数据draft_tree_data_support时的插入。moveAtomicBlock 场景折叠选区下移动到块首 / 块尾 / 块中间 / 块前 / 块后非折叠选区下的替换式移动块首、块尾、块中间before、after显式模式非法场景移动到自身附近抛错实验性树形数据下的移动。测试通过快照src/model/modifier/tests/snapshots/AtomicBlockUtils-test.js.snap比对移动前后整个 BlockMap 的序列化结果确保每种场景下块顺序、块类型、实体关联都精确符合预期。渲染侧配套blockRendererFn 与 atomic 块插入原子块只是第一步编辑器还需要告诉 Draft.js 如何渲染它。参照 examples/draft-0-10-0/media/media.html通过Editor的blockRendererFn属性拦截atomic类型块function mediaBlockRenderer(block) { if (block.getType() atomic) { return { component: Media, editable: false, // 原子块不可编辑 }; } return null; }在自定义组件Media中通过props.contentState.getEntity(props.block.getEntityAt(0))取回实体再根据entity.getType()与entity.getData()渲染音视频或图片media 示例中audio/image/video三种类型分别映射到audio/img/video。TeX 示例的组件实现见 examples/draft-0-10-0/tex/js/components/TeXBlock.js。典型应用场景小结场景推荐方法参考实现工具栏点击插入图片/音视频/公式insertAtomicBlock(editorState, entityKey, )examples/draft-0-10-0/media/media.html、examples/draft-0-10-0/tex/js/modifiers/insertTeXBlock.js拖拽/代码中将原子块移到指定块前moveAtomicBlock(editorState, block, range, before)src/model/modifier/AtomicBlockUtils.js拖拽/代码中将原子块移到指定块后moveAtomicBlock(editorState, block, range, after)同上用原子块替换选中内容moveAtomicBlock(editorState, block, range)默认 replace同上关键注意事项character必须传空格 空字符串会导致Unknown DraftEntity key: null错误entityKey必须来自同一个ContentState创建实体后要通过EditorState.set(editorState, {currentContent: contentStateWithEntity})重建状态再调用insertAtomicBlockmoveAtomicBlock不允许将块移动到自身旁边会抛出 invariant 异常原子块插入时会自动附带一个unstyled分隔块用于放置光标继续输入不要误以为是 bug原子块在渲染层需要配合blockRendererFneditable: false的自定义组件才能正确展示。通过AtomicBlockUtils你可以用极少的代码为 Draft.js 编辑器构建出完整的富媒体插入与重排能力其背后的不可变数据管线和DraftModifier组合模式也是理解 Draft.js 编辑操作如何被组合、推入撤销栈的绝佳范例。【免费下载链接】draft-jsA React framework for building text editors.项目地址: https://gitcode.com/gh_mirrors/dr/draft-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考