Lexical:Meta 重新发明的 Web 文本编辑器框架

React
富文本
编辑器
Meta
开源
前端
2026/9/30
·

阅读时间: 大约 10 分钟

Lexical:Meta 重新发明的 Web 文本编辑器框架

Lexical 官网首页的在线编辑器:仅一个极简工具栏(Normal 段落样式、撤销重做、B/I/U、列表),输入区写着 "Enter some text..."

Lexical 是 Meta 开源的 Web 文本编辑器框架,官方定位是”an extensible text editor framework for the web, built for reliability, accessibility, and performance”。据 Koala 项目库介绍,它是 Meta 在总结自家过往富文本方案(如 Draft.js)的不足后重新设计的产物。今天它不只是一个”Demo 编辑器”——官方文档称它驱动着 Meta 自家 Web 产品、Bloomberg,以及 Payload CMS、Supabase、Proton Docs、MDXEditor、Dify、RAGFlow、DeepSeek Harness 等项目的文本编辑。本文基于 Lexical 官方文档,分析它的核心设计、与传统方案的结构差异,以及它刻意”不做什么”。

一、背景:contenteditable 是个坑

Web 上做富文本编辑器,绕不开浏览器原生的 contenteditable。它能跑,但行为极不一致:不同浏览器对回车、删除、选区、嵌套标签的处理各有怪癖,同样一段”加粗斜体”可以被序列化成好几种不同的 HTML。直接操作 DOM 来维护编辑器,等于在一堆未定义行为上盖楼。

Meta 的回答是:不要让 DOM 成为真相来源。Lexical 虽然仍挂在一个 contenteditable 元素上,但它自己维护一套文档模型,所有读写都经过这套模型,再由框架负责把模型同步回 DOM。

二、是什么:一个”内核”,不是一个”开箱编辑器”

需要先建立正确预期:Lexical 不直接给你带工具栏的成品编辑器。官方原话是——“Lexical supplies the editing infrastructure. Your application supplies the layout, toolbars, menus, styling, and storage.”(Lexical 提供编辑基础设施,布局、工具栏、菜单、样式、存储都由你的应用提供)。

  • lexical 包是核心:编辑器、编辑器状态、基础节点类型、选区、命令与 DOM reconciler,零依赖、框架无关;
  • 其余功能都是可选包,按需引入列表、链接、表格等;React 绑定在 @lexical/react;
  • 新版引入了扩展(extension)模型:一个扩展把某功能所需的节点、配置、命令、监听器及其依赖打包在一起,一处挂载即可。

这意味着你可以拿它做一个只支持 @提及和 #话题 的单行输入框,也可以拼出一个带表格、代码块、图片的完整文档编辑器——它是底座,不是成品。

三、技术机制:不可变状态 + 扁平化节点树

3.1 EditorState 才是真相

官方文档明确:“the source of truth is not the DOM, but rather an underlying state model”。一个 EditorState 是文档的不可变快照:一棵挂在单个 RootNode 下的节点树,外加一个选区。更新时它短暂可变,提交后即被锁定为快照。editor.getEditorState() 返回已提交状态,toJSON() 用于保存/恢复(注意:序列化只含节点树,不含选区与运行时节点 key)。

3.2 格式是属性,不是嵌套元素

这是 Lexical 与传统 HTML/mark-based 编辑器最本质的区别。官方举了个例子:下面三段 HTML 渲染出来完全一样,但 DOM 结构不同:

<i><b>Lexical</b></i>
<b><i>Lexical</i></b>
<i><b>Lex<b><b>ical</b></i>

官方文档图示:同一句含加粗+斜体的文本,在传统 HTML 中因嵌套顺序不同而产生多种不同的嵌套结构

Lexical 的做法是把结构与格式解耦:段落的 children 是一串扁平的文本节点,加粗/斜体是每个文本节点上的属性(如 {format: bold, italic})。于是无论用户先加粗还是先斜体,文档树都只有一种规范形态——这正是它区别于 mark-based(如 ProseMirror)编辑器的地方:Lexical 的节点树形状贴近它所渲染的 HTML。

3.3 更新、批量与 DOM 对账

  • 所有读写都发生在同步回调里(editor.update(fn) / editor.read(...));以 $ 开头的 API(如 $getRoot())只能在这类回调里用,官方类比 React Hooks 只能在渲染期调用;
  • 同一 tick 内的多次更新会被批量成一个 pending 副本;提交时 reconciler 只 patch 属于变更节点的那部分 DOM;
  • Lexical 还会监听它职责之外的 DOM 改动,决定保留还是回滚。

行为由三类钩子拼装:Commands(按优先级把用户输入/自定义动作分发给处理器)、Node transforms(在更新期保持文档形状,如 Markdown 输入 # 自动转标题)、Listeners(提交后响应,如联动预览)。

3.4 可序列化、可在服务端跑

内容可在 JSON / HTML / Markdown 之间互转;一个不挂 root 元素的编辑器完全不做 DOM 工作,因此同一份代码能跑在服务器或测试环境里——这对 SSR 和服务端渲染 Markdown 很关键。实时协作则通过 Yjs 集成实现远程光标与共享内容。

四、关键口径:它”快”在哪里

Lexical 首页把 Reliable / Accessible / Fast 列为三大卖点,但官方并未给出吞吐 benchmark。这里要把口径说清楚:它所说的”快”,不是某个量化数字,而是一组工程机制——只 patch 变更节点的 DOM、同 tick 批量更新、把重 UI 职责移出核心包。它遵循 WCAG、兼容屏幕阅读器,这部分是可验证的设计取向,而非压测结论。

五、适用 / 不适用场景

适合:

  • 需要把文本输入嵌入自家产品、并高度定制交互的团队(评论框、IM 输入、Notion 式块编辑器、CMS 文档编辑器);
  • 需要实时协作编辑(Yjs)或服务端序列化的场景;
  • 已经在用 React、愿意自己组装工具栏的团队。

不适合:

  • 想要”下载即用、自带美观工具栏”的成品富文本组件——Lexical 不提供这些;
  • 只需要一个多行文本框、却不想引入一套节点树心智的极简单场景。

六、客观分析:优势与局限

优势:

  1. 真相来源清晰:不可变状态 + toJSON(),文档可保存、可回放、可时间旅行,调试比直改 DOM 容易得多;
  2. 规范的格式模型:把格式下沉为文本节点属性,消除了嵌套 HTML 的歧义;
  3. 按需组装、包体可控:零依赖核心,不用的功能不打进包里;
  4. 工业界背书:Meta、Bloomberg 及一大批知名产品在用,不是玩具项目。

局限(含官方口径):

  1. 不做 UI 是双刃剑:官方明确不管工具栏、菜单、Markdown 渲染——自由度高,但意味着你要自己拼大量周边,上手成本比成品编辑器高;
  2. 心智约束:$ API 只能在更新回调内调用、读写必须走同步回调,对习惯了随手改 DOM 的开发者是一套新纪律;
  3. 与 mark-based 生态的差异:它的节点树贴近 HTML,与基于 mark 的方案在数据模型上不同,迁移已有编辑器的内容/插件时要做模型转换;
  4. “快”无官方量化背书:性能优势来自架构机制,具体到你的复杂文档,仍需自己实测。

七、它意味着什么 / 谁该关注

Lexical 代表了富文本编辑器的一条”底层重造”路线:不追求开箱即用,而是把文档模型、更新调度、DOM 对账这些最难、最易出 bug 的部分做扎实,再把体验层完全开放。如果你正在做一个长期演进的内容产品、需要协作或服务端渲染,并且有能力/意愿自建工具栏,它是目前最值得认真评估的开源选项之一;如果只是要一个”能加粗斜体的评论框”,它的抽象可能过重。

参考来源