资讯动态

深入理解 @lexical/rich-text:Lexical 富文本编辑器的命令集与内置扩展

发布时间:2026/9/12 3:31:32 来源:尧图企业网站定制
深入理解 lexical/rich-textLexical 富文本编辑器的命令集与内置扩展【免费下载链接】lexicalLexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.项目地址: https://gitcode.com/GitHub_Trending/le/lexical导读lexical/rich-text是 Lexical 生态中面向富文本场景的“开箱即用”包它以一组基础命令监听器为起点覆盖文本输入、字符删除、复制粘贴、方向键移动选区等基础编辑行为并为标题、文本格式与块引用等富文本特性提供默认实现。本文将以 packages/lexical-rich-text/README.md 为主体结合该包的源码、配置与测试讲解它的设计定位、核心命令体系、可配置项以及可扩展方式帮助你理解“何时选用它”“它替你做完了什么”“如何在它之上做定制”。一、包定位给编辑器一套“默认键位与行为”在 Lexical 的架构中核心包lexical只负责编辑器状态模型、节点树、命令分发与生命周期它不预设任何键盘行为或编辑习惯。具体的输入体验由各个功能包以“注册命令监听器”的方式提供。lexical/rich-text正是这样一个“起始点”包它注册了一组基础命令的监听器覆盖简单文本编辑行为——输入文字、删除字符、复制粘贴、用方向键改变选区同时为富文本特性提供默认行为——标题、格式化文本与块引用。这意味着你不需要自己写KEY_BACKSPACE_COMMAND、DELETE_CHARACTER_COMMAND等监听逻辑你可以把该包当作地基在此基础上追加自己的命令监听器来定制编辑器功能追加监听器的优先级、顺序由你控制如果你不需要富文本能力纯文本输入即可官方建议改用 lexical/plain-text它的体积和行为都更精简。从仓库中packages/lexical-rich-text/package.json的dependencies可以看到它的能力来源lexical/clipboard剪贴板、lexical/selection选区运算、lexical/utils工具函数、lexical/dragon语音/听写输入支持、lexical/a11y无障碍以及核心包lexical。二、两种接入方式扩展 API 与命令注册函数当前仓库中的lexical/rich-text同时提供两代接入方式。2.1 扩展方式RichTextExtension推荐在基于扩展体系buildEditorFromExtensions/defineExtension构建编辑器时直接声明依赖即可import {RichTextExtension} from lexical/rich-text; import {HistoryExtension} from lexical/history; import {buildEditorFromExtensions} from lexical; const editor buildEditorFromExtensions( { namespace: MyRichEditor, theme: {...}, }, [RichTextExtension, HistoryExtension], );从 LexicalRichTextExtension.ts 的源码可以看到RichTextExtension并非孤立扩展它会自动拉起一组配套依赖HeadingAnnounceExtension无障碍播报详见下文第六节DragonExtensionDragon 语音听写输入支持NormalizeInlineElementsExtension/NormalizeTripleClickSelectionExtension行内元素规范与三击全选规范化CoreImportExtension 定制了规则的DOMImportExtension通过RichTextImportRules支持从 HTML 导入标题与块引用见第七节。扩展还声明了conflictsWith: [lexical/plain-text]即纯文本包与富文本包互斥二者不可同时挂载。2.2 命令注册方式registerRichText对于基于createEditor()的传统写法包导出了函数registerRichText(editor, escapeFormatTriggers?, shouldHandlePasteAsFiles?)它批量注册全部命令监听并返回一个清理函数import {createEditor} from lexical; import {registerRichText} from lexical/rich-text; const editor createEditor({namespace: MyEditor}); const removeListeners registerRichText(editor); // 卸载时调用 removeListeners()函数的第二、三个可选参数与扩展的配置一一对应见第五节。该函数的完整实现在 index.ts一个大型mergeRegister(...)包裹了下面第三节列出的全部命令。三、核心命令体系包替你监听了什么这是本包的技术核心。registerRichText在COMMAND_PRIORITY_EDITOR优先级上注册了如下命令监听以下命令常量均来自核心包lexical完整实现见 index.ts命令默认行为说明CLICK_COMMAND清空NodeSelection按触发配置逃逸文本格式点击已选中节点内部视为“与节点交互”不取消选中DELETE_CHARACTER_COMMANDselection.deleteCharacter(isBackward)/deleteNodes()删除字符或节点选择DELETE_WORD_COMMANDselection.deleteWord(isBackward)删除单词DELETE_LINE_COMMANDselection.deleteLine(isBackward)删除整行CONTROLLED_TEXT_INSERTION_COMMAND插入文本 / 富文本 DataTransfer受控文本插入含 beforeinput 路径REMOVE_TEXT_COMMANDselection.removeText()删除选中文本FORMAT_TEXT_COMMAND$formatText(selection, format)文本级格式加粗、斜体等SET_TEXT_FORMAT_COMMAND$setTextFormat(selection, formats)批量设置文本格式FORMAT_ELEMENT_COMMAND对最近的块级祖先设置setFormat块级对齐左/中/右/两端INSERT_LINE_BREAK_COMMANDselection.insertLineBreak(selectStart)插入换行ShiftEnterINSERT_PARAGRAPH_COMMANDselection.insertParagraph()插入段落EnterINSERT_TAB_COMMAND插入TabNode插入制表符节点INDENT_CONTENT_COMMAND/OUTDENT_CONTENT_COMMANDblock.setIndent(indent ± 1)块缩进 / 反缩进KEY_ARROW_UP/DOWN/LEFT/RIGHT_COMMAND移动选区处理 NodeSelection→RangeSelection 转换、RTL 方向、块光标、装饰器与行内网格导航方向键导航KEY_BACKSPACE_COMMAND/KEY_DELETE_COMMAND转发为DELETE_CHARACTER_COMMAND缩进块开头 Backspace 触发反缩进iOS 特殊处理删除键KEY_ENTER_COMMANDShift 判定后转发INSERT_LINE_BREAK_COMMAND或INSERT_PARAGRAPH_COMMAND回车KEY_ESCAPE_COMMANDeditor.blur()Esc 失焦KEY_SPACE_COMMAND/KEY_TAB_COMMAND触发格式逃逸检查然后交还默认行为空格 / TabDROP_COMMAND/DRAGSTART_COMMAND/DRAGOVER_COMMAND文件拖放转发为DRAG_DROP_PASTE把 Lexical 自有序列化写入 DataTransfer拖放SELECT_ALL_COMMAND全选仅在具名插槽内有界全选COPY_COMMAND/CUT_COMMAND/PASTE_COMMAND复制 / 剪切 / 粘贴富文本剪贴板详见下文MOVE_TO_END/MOVE_TO_START光标移动到块首/块尾绕过 Chromium 对contenteditablefalse行内装饰器的边界限制行首行尾其中几个值得展开的细节剪贴板三件套。粘贴路径onPasteForRichText会调用$insertDataTransferForRichText把剪贴板中的 HTML/文本按富文本规则插入并以PASTE_TAG作为撤销边界来源注释说明这是让“撤销粘贴”不会连带撤销粘贴前的输入见 index.ts。剪切路径onCutForRichText会先把整文档选区扩展到块本身再复制保证 CmdX 后 CmdV 能还原标题、引用或列表这类块结构而非只还原文本同样用CUT_TAG标记为独立撤销条目index.ts。拖拽起点DRAGSTART_COMMAND会把 Lexical 自有序列化写入 DataTransfer使自定义节点图片、装饰器在编辑器间拖放时不会降级为纯 HTML。Backspace 的缩进语义当光标位于缩进块的开头时按下 Backspace 会先preventDefault并转发OUTDENT_CONTENT_COMMAND即“先反缩进再删字符”与主流编辑器的直觉一致index.ts。平台兼容KEY_BACKSPACE_COMMAND在 iOS beforeinput 环境下特意返回false不阻断 keydown以免干扰系统键盘的自动更正建议栏KEY_ENTER_COMMAND对 iOS/Safari/WebKit 同样放行默认行为让自动完成、自动大写正常工作源码注释引用了对应 issue 编号。四、富文本节点HeadingNode 与 QuoteNode包通过扩展注册了两个富文本核心节点见 LexicalRichTextExtension.ts 的nodes: () [HeadingNode, QuoteNode]。4.1 HeadingNode标题节点HeadingNode对应h1–h6其行为要点见 index.ts构造函数接受HeadingTagType h1 | h2 | ... | h6默认h1createDOM根据__tag创建对应标签并从主题中取theme.heading[tag]应用类名updateDOM在标签变化时返回true触发 DOM 更新insertNewAfter实现了标题拆分语义光标不在末尾时按 Enter 会把标题从中间拆成两段后半段保留标题标签、格式与样式光标在末尾时插入普通段落importDOM为h1–h6提供导入转换并包含一个有趣的 Google Docs 标题启发式当p首子节点或span具有font-size: 26pt时将其视为来自 Google Docs 的文档标题并转换为h1index.ts。程序化创建/判断import {$createHeadingNode, $isHeadingNode} from lexical/rich-text; editor.update(() { const h2 $createHeadingNode(h2); // 插入到根节点... });4.2 QuoteNode块引用节点QuoteNode渲染为blockquote并支持一个可选的“影子根shadow root”行为见 index.ts默认shadowRoot: false维持传统行为引用内部持有行内内容通过$createQuoteNode({shadowRoot: true})或node.setIsShadowRoot(true)可选用影子根模式此时引用像一个多块区域类似表格单元格内部持有段落、标题等块级子节点从而让blockquote的 HTML/Markdown 导入导出保真影子根模式下光标在引用开头按 Backspace 会“解散”引用并把内部块提升为兄弟节点而非合并成单个段落collapseAtStart实现。程序化创建import {$createQuoteNode, $isQuoteNode} from lexical/rich-text; editor.update(() { const quote $createQuoteNode(); // 或 $createQuoteNode({shadowRoot: true}) 启用影子根模式 });对应的单元测试位于 LexicalQuoteNode.test.ts验证了节点类型、createDOM生成blockquote classmy-quote-class的类名注入、updateDOM返回false无需更新等行为。4.3 文本格式FORMAT_TEXT_COMMAND/SET_TEXT_FORMAT_COMMAND覆盖的文本格式类型为TextFormatTypebold、italic、underline、strikethrough、code、subscript、superscript、highlight、lowercase、uppercase、capitalize等由核心包定义。工具栏按钮通常就是editor.dispatchCommand(FORMAT_TEXT_COMMAND, bold)。五、可配置项格式逃逸与粘贴为文件RichTextConfig提供了两个运行时可调配置定义于 LexicalRichTextExtension.ts。5.1escapeFormatTriggers格式逃逸触发器“格式逃逸”指当光标带某种文本格式时在某些用户交互回车、点击、方向键、空格、Tab下自动清除该格式避免用户把格式“带入”下一段。触发类型enter | click | arrow | space | tab每个格式可配onlyAtBoundary为true时仅在光标位于格式化文本节点首/尾且该方向没有相邻兄弟时才逃逸为false/缺省时无论光标位置都逃逸对应历史$resetCapitalization行为默认配置只对capitalize、lowercase、uppercase三种格式生效{ capitalize: {enter: true, space: true, tab: true}, lowercase: {enter: true, space: true, tab: true}, uppercase: {enter: true, space: true, tab: true}, }通过configExtension可追加其他格式的逃逸规则例如让code格式在文本节点边界处随 Enter/点击/方向键逃逸import {RichTextExtension} from lexical/rich-text; import {configExtension} from lexical; configExtension(RichTextExtension, { escapeFormatTriggers: { code: {onlyAtBoundary: true, enter: true, click: true, arrow: true}, }, });配置采用浅合并mergeEscapeFormatTriggers会按格式逐项合并TriggerConfig若某个格式传null则显式禁用该格式的逃逸用于覆盖默认值。逃逸的实际判定逻辑$escapeFormatsForTrigger在 index.ts它判断选区是否为折叠的文本点、是否处于边界然后对命中的格式执行selection.toggleFormat。5.2shouldHandlePasteAsFiles粘贴文件判定该回调决定当剪贴板同时携带文件与文本时粘贴事件是否优先走DRAG_DROP_PASTE文件通道。签名type ShouldHandlePasteAsFiles ( files: File[], hasTextContent: boolean, ) boolean;默认实现defaultShouldHandlePasteAsFiles保持历史行为——仅当剪贴板完全不含文本内容时才按文件处理index.ts。源码注释特别指出浏览器在“右键复制图片”时往往会在文件旁附带 text/html 兜底因此默认规则下这类图片会走 HTML 导入器。需要“有文本也当文件粘贴”时可自定义该回调。六、无障碍扩展HeadingAnnounceExtension富文本编辑器里一个常见的可访问性痛点是屏幕阅读器用户无法感知“这一块变成了标题”。HeadingAnnounceExtensionHeadingAnnounceExtension.ts通过AriaLiveRegionExtension的实时区播报两类转换块变为标题时播报created默认文案Heading level %s%s替换为 1–6 的级别标题被移除时播报destroyed默认Heading level %s removed。设计细节值得注意只播报“变成标题/不再是标题”两种转换光标在标题内移动、输入、删除不播报避免每次按键都打断用户级别变化会同时触发“移除创建”代码优先播报创建事件避免播报旧级别被移除节点的级别从prevEditorState读取通过信号disabled可在运行时关闭关闭时不注册任何监听器文案模板支持运行时修改且只在播报时读取修改不会导致监听器重注册。这一扩展由RichTextExtension自动依赖无需单独配置。七、HTML 导入规则RichTextImportRules 与影子根引用随着扩展体系引入lexical/rich-text额外导出了基于DOMImportExtension的导入规则集合RichTextImportRulesRichTextImportExtension.ts包含四条规则规则匹配元素行为HeadingRuleh1–h6创建对应HeadingNode还原缩进、格式与方向QuoteRuleblockquote创建默认QuoteNodeGoogleDocsTitleParagraphRulep若首子节点是 26pt 的标题 span则丢弃该段落包装GoogleDocsTitleSpanRulespan26pt 的 span 提升为h1这些规则由RichTextExtension自身连同CoreImportExtension注册因此任何使用富文本扩展的编辑器都能直接通过DOMImportExtension管线导入这些标签无需额外配置标注为experimental。此外还有一个可选的ShadowRootQuoteRule它把blockquote导入为影子根QuoteNode并用BlockSchema保留块级子节点使结构化 blockquote 的 HTML 往返导入导出不被打平成行内内容。它默认不启用启用方式是在规则编译顺序上“压过”默认规则例如buildEditorFromExtensions( MyExtension, configExtension(DOMImportExtension, {rules: [ShadowRootQuoteRule]}), );八、典型使用示例一个富文本编辑器的完整骨架参考仓库中 examples/website-toolbar/src/Editor.tsx 的实际用法一个带工具栏的富文本编辑器可以这样组织扩展import {RichTextExtension} from lexical/rich-text; import {HistoryExtension} from lexical/history; import {TabIndentationExtension} from lexical/tab-indentation; import {buildEditorFromExtensions} from lexical; const editor buildEditorFromExtensions( { namespace: RichTextDemo, theme: { heading: {h1: text-3xl font-bold, h2: text-2xl font-semibold}, quote: border-l-4 pl-4 text-gray-600, }, }, [ RichTextExtension, // 富文本默认行为 Heading/Quote 节点 HistoryExtension, // 撤销/重做 TabIndentationExtension, // Tab 缩进 // 再追加你自己的业务扩展…… ], );之后工具栏按钮通过editor.dispatchCommand(...)触发富文本行为editor.dispatchCommand(FORMAT_TEXT_COMMAND, bold); editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, center); editor.dispatchCommand(OUTDENT_CONTENT_COMMAND);在 examples/website-toolbar/src/tests/browser/editor.test.ts 中可以看到其浏览器级测试同样以dependencies: [RichTextExtension]构建测试编辑器验证默认行为。九、与 lexical/plain-text 的选择lexical/rich-text与 lexical/plain-text 共享同一套“命令起始点”设计哲学区别在于rich-text额外提供标题、块引用、块级格式化等富文本语义并注册FORMAT_ELEMENT_COMMAND、INSERT_TAB_COMMAND等富文本相关命令plain-text只保留纯文本输入体验不注册富文本命令体积与行为更克制两者在扩展体系中互斥conflictsWith同一编辑器不可同时挂载。选择依据很简单需要标题/引用/块格式就用 rich-text只需要像textarea一样的输入体验就用 plain-text。十、从源码结构看包的演进与边界最后从源码结构总结一下这个包的边界与演进方向包根导出位于 src/index.ts统一导出节点类、工厂函数$createHeadingNode、$createQuoteNode、类型守卫$isHeadingNode、$isQuoteNode、命令注册函数registerRichText、配置类型RichTextConfig、扩展RichTextExtension/RichTextImportExtension以及导入规则新增的扩展层代码独立成 LexicalRichTextExtension.ts 与 HeadingAnnounceExtension.ts体现了仓库正在把“散落的注册逻辑”收敛为声明式扩展的趋势测试覆盖相当完整src/__tests__/unit/下有LexicalHeadingNode、LexicalQuoteNode、LexicalTabNode、EscapeFormatTriggers、QuoteInsertNewAfter等单元测试src/__tests__/browser/下有针对方向键、Backspace、Enter、粘贴文件等交互的浏览器测试可作为理解各命令边界行为的“行为说明书”。使用建议新项目优先走RichTextExtension接入需要精细控制优先级或不想引入扩展体系时退回到registerRichText(editor)手动注册在富文本之上做业务扩展时继续追加你自己的editor.registerCommand(..., COMMAND_PRIORITY_EDITOR)监听即可——这正符合该包“起始点”的设计定位。【免费下载链接】lexicalLexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.项目地址: https://gitcode.com/GitHub_Trending/le/lexical创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价