Draft.js AtomicBlockUtils 完全指南:在编辑器中插入与移动原子块(Atomic Block)
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),仅供参考

相关新闻

Ray 分布式 multiprocessing.Pool:用一行 import 将 Python 多进程程序扩展到集群

Ray 分布式 multiprocessing.Pool:用一行 import 将 Python 多进程程序扩展到集群

Ray 分布式 multiprocessing.Pool:用一行 import 将 Python 多进程程序扩展到集群 【免费下载链接】ray Ray is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads. 项目地址: https:/…

2026/9/20 23:55:58 阅读更多 →
Hero 开源库 CHANGELOG 深度解读:从 1.3.0 到 1.6.3 的版本演进与核心源码实现

Hero 开源库 CHANGELOG 深度解读:从 1.3.0 到 1.6.3 的版本演进与核心源码实现

Hero 开源库 CHANGELOG 深度解读:从 1.3.0 到 1.6.3 的版本演进与核心源码实现 【免费下载链接】Hero Elegant transition library for iOS & tvOS 项目地址: https://gitcode.com/gh_mirrors/he/Hero Hero 是面向 iOS 与 tvOS 的优雅转场动画库&#xf…

2026/9/20 23:55:58 阅读更多 →
grok-build v0.2.87 版本解读:自动订阅升级、/docs 导航与按模型推理强度配置

grok-build v0.2.87 版本解读:自动订阅升级、/docs 导航与按模型推理强度配置

grok-build v0.2.87 版本解读:自动订阅升级、/docs 导航与按模型推理强度配置 【免费下载链接】grok-build SpaceXAIs coding agent harness and TUI. Fullscreen, mouse interactive, extensible. 项目地址: https://gitcode.com/gh_mirrors/gr/grok-build …

2026/9/22 1:59:34 阅读更多 →

最新新闻

3分钟搞定登入成语:源码解析+移动端实战避坑指南

3分钟搞定登入成语:源码解析+移动端实战避坑指南

3分钟搞定登入成语:源码解析+移动端实战避坑指南 看着满屏红色的 StackTrace ,是不是脑子嗡嗡作响?别慌,这通常是新手在 登入成语 相关开发中遇到的典型场景,尤其是当业务逻辑与底层源码交互出错时。…

2026/9/22 2:00:04 阅读更多 →
避坑奥兹恩:从入门到精通的实战血泪史

避坑奥兹恩:从入门到精通的实战血泪史

避坑奥兹恩:从入门到精通的实战血泪史 看了一堆教程还是不会写项目,这是很多开发者卡在“奥兹恩”技术栈时的真实写照。你以为背下了文档里的 API 就万事大吉了?现实是,一上手真实业务,各种隐蔽的 Bug 和性能陷阱就接踵而至。…

2026/9/22 2:00:04 阅读更多 →
手机qq音乐避坑指南:5个必改的Bug让代码跑通

手机qq音乐避坑指南:5个必改的Bug让代码跑通

手机qq音乐避坑指南:5个必改的Bug让代码跑通 刚毕业进组,对着文档敲下的代码运行直接报错,心里慌得一批?别急,这是每个新手的必经之路。 今天不讲虚的,只聊怎么把复制来的手机QQ音乐API调用代码调通。…

2026/9/22 1:59:04 阅读更多 →
搞懂健身教练要求这3点,前端实战项目不再踩坑

搞懂健身教练要求这3点,前端实战项目不再踩坑

搞懂健身教练要求这3点,前端实战项目不再踩坑 刚入行前端,或者从其他行业转行过来,是不是经常陷入这种尴尬:语法背得滚瓜烂熟,LeetCode 刷了大半本,但一让你做一个 实战项目 ,脑子就一片空白?…

2026/9/22 1:59:04 阅读更多 →
3步搞定WMF格式解析,一文搞懂原理与实战避坑

3步搞定WMF格式解析,一文搞懂原理与实战避坑

3步搞定WMF格式解析,一文搞懂原理与实战避坑 刚入职那会儿,我接手一个老旧政府系统的文档转换需求,结果在WMF格式上卡了整整三天。 配置环境就卡半天…

2026/9/22 1:59:04 阅读更多 →
瓜帅考试避坑指南:5个面试必问底层原理

瓜帅考试避坑指南:5个面试必问底层原理

瓜帅考试避坑指南:5个面试必问底层原理 看了一堆瓜帅教程还是不会写项目?别急,这锅不全是你的。很多技术老手在复盘时发现,卡住你的往往不是语法,而是那些 面试必问…

2026/9/22 1:59:04 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →