web-editor-lexical
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLexical Editor Patterns
Lexical 编辑器模式
Quick Guide: Lexical is a lightweight (22kb min+gzip) extensible text editor framework. Usefor React setup with plugins as child components. Extend via the node system (ElementNode, TextNode, DecoratorNode), command system (createCommand + priorities), and transforms. EditorState is immutable -- all mutations happen insideLexicalComposer. Useeditor.update()-prefixed functions only inside update/read closures. Current: v0.42.x (pre-1.0)$
<critical_requirements>
快速指南: Lexical是一个轻量级(压缩后仅22kb)的可扩展文本编辑器框架。在React环境中使用进行搭建,将插件作为子组件使用。可通过节点系统(ElementNode、TextNode、DecoratorNode)、命令系统(createCommand + 优先级)和转换机制进行扩展。EditorState是不可变的——所有修改操作都必须在LexicalComposer内部执行。仅在update/read闭包内使用editor.update()前缀的函数。当前版本:v0.42.x(1.0正式版前)$
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用本技能前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST call -prefixed functions (, , ) ONLY inside or closures -- calling them outside throws runtime errors)
$$getRoot$getSelection$createTextNodeeditor.update()editor.read()(You MUST register custom nodes in the array of -- unregistered nodes cause silent failures or runtime errors)
nodesinitialConfig(You MUST return a cleanup function from when registering commands, transforms, or listeners -- Lexical register methods return unsubscribe functions)
useEffect(You MUST include preconditions in transforms to prevent infinite loops -- a transform that unconditionally modifies its target node re-triggers itself)
</critical_requirements>
Auto-detection: Lexical, lexical, @lexical/react, @lexical/rich-text, @lexical/list, @lexical/code, @lexical/link, @lexical/html, @lexical/headless, LexicalComposer, EditorState, LexicalNode, ElementNode, TextNode, DecoratorNode, createCommand, dispatchCommand, registerCommand, COMMAND_PRIORITY, $getRoot, $getSelection, $createParagraphNode, $createTextNode, RichTextPlugin, OnChangePlugin, HistoryPlugin, useLexicalComposerContext, editor.update, editor.read, registerNodeTransform, exportJSON, importJSON, exportDOM, importDOM, NodeState, createState
When to use:
- Building rich text editors with custom formatting and embedded content
- Creating editors with custom node types (mentions, embeds, code blocks)
- Implementing collaborative editing with operational transforms
- Building structured content editors (not just plain text)
When NOT to use:
- Plain text inputs without formatting (use a standard )
<textarea> - Simple markdown editing without live preview (use a textarea with markdown parsing)
- Editors that need pure decorations without document mutation (Lexical decorator nodes mutate content)
Key patterns covered:
- React setup with LexicalComposer, plugins, and initialConfig
- Node system: ElementNode, TextNode, DecoratorNode, custom nodes
- Command system: createCommand, priorities, dispatching, propagation
- Transforms for automatic node mutations
- EditorState immutability and the update lifecycle
- JSON and HTML serialization
Detailed Resources:
- examples/core.md - Editor setup, plugins, commands, transforms
- examples/custom-nodes.md - Custom ElementNode, TextNode, DecoratorNode, NodeState API
- examples/serialization.md - JSON/HTML serialization, import/export, headless usage
- reference.md - Decision frameworks, command priority table, anti-patterns
<philosophy>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名法、具名导出、导入顺序、、具名常量)import type
(你必须仅在或闭包内调用前缀的函数(如、、)——在外部调用会触发运行时错误)
editor.update()editor.read()$$getRoot$getSelection$createTextNode(你必须在的数组中注册自定义节点——未注册的节点会导致静默失败或运行时错误)
initialConfignodes(注册命令、转换或监听器时,必须从中返回清理函数——Lexical的注册方法会返回取消订阅函数)
useEffect(转换中必须包含前置条件以防止无限循环——无条件修改目标节点的转换会反复触发自身)
</critical_requirements>
自动检测关键词: Lexical、lexical、@lexical/react、@lexical/rich-text、@lexical/list、@lexical/code、@lexical/link、@lexical/html、@lexical/headless、LexicalComposer、EditorState、LexicalNode、ElementNode、TextNode、DecoratorNode、createCommand、dispatchCommand、registerCommand、COMMAND_PRIORITY、$getRoot、$getSelection、$createParagraphNode、$createTextNode、RichTextPlugin、OnChangePlugin、HistoryPlugin、useLexicalComposerContext、editor.update、editor.read、registerNodeTransform、exportJSON、importJSON、exportDOM、importDOM、NodeState、createState
适用场景:
- 构建带有自定义格式和嵌入内容的富文本编辑器
- 创建带有自定义节点类型(提及、嵌入内容、代码块)的编辑器
- 实现基于操作转换的协同编辑功能
- 构建结构化内容编辑器(而非纯文本编辑器)
不适用场景:
- 无需格式的纯文本输入(使用标准即可)
<textarea> - 无需实时预览的简单Markdown编辑(使用带Markdown解析的文本域)
- 仅需纯装饰效果而不修改文档内容的编辑器(Lexical的装饰节点会修改内容)
涵盖的核心模式:
- 使用LexicalComposer、插件和initialConfig进行React环境搭建
- 节点系统:ElementNode、TextNode、DecoratorNode、自定义节点
- 命令系统:createCommand、优先级、调度、传播
- 用于自动节点修改的转换机制
- EditorState不可变性与更新生命周期
- JSON和HTML序列化
详细资源:
- examples/core.md - 编辑器搭建、插件、命令、转换
- examples/custom-nodes.md - 自定义ElementNode、TextNode、DecoratorNode、NodeState API
- examples/serialization.md - JSON/HTML序列化、导入/导出、无头模式使用
- reference.md - 决策框架、命令优先级表、反模式
<philosophy>
Philosophy
设计理念
Lexical is an editor framework, not a batteries-included editor. The core is intentionally minimal -- it provides the node tree, selection, reconciler, command system, and update lifecycle. Everything else (toolbars, formatting, lists, links, embeds) is a plugin.
Key architectural principles:
- Immutable EditorState: The editor maintains a frozen state snapshot. Mutations happen inside closures that clone the state, apply changes, then reconcile to DOM.
editor.update() - -function convention: Functions prefixed with
$(like$,$getRoot()) must run inside$getSelection()oreditor.update()closures -- similar to React hooks requiring a component context.editor.read() - Plugin = React component: In the React binding, a plugin is a React component rendered as a child of . It accesses the editor via
<LexicalComposer>and registers commands/transforms/listeners inuseLexicalComposerContext().useEffect - Command-driven architecture: User interactions and plugin communication flow through typed commands with priority-based listeners, enabling plugins to intercept or augment behavior.
- Node-driven content model: Content is a tree of typed nodes. Custom content types (mentions, embeds, polls) are custom node classes.
When to use Lexical:
- Rich text editing with custom formatting and embedded content
- Content editors requiring structured output (not just HTML strings)
- Editors needing accessibility and screen reader support
- Applications requiring server-side rendering or headless processing of editor content
When NOT to use Lexical:
- Simple text inputs (a is simpler and lighter)
<textarea> - Editors needing pure decorations that don't affect document content
- Projects requiring a stable 1.0 API (Lexical is pre-1.0, APIs may change)
<patterns>
Lexical是一个编辑器框架,而非开箱即用的编辑器。核心部分刻意保持极简——它提供节点树、选择器、协调器、命令系统和更新生命周期。其他所有功能(工具栏、格式、列表、链接、嵌入内容)均以插件形式存在。
核心架构原则:
- 不可变EditorState: 编辑器维护一个冻结的状态快照。修改操作在闭包内执行,该闭包会克隆状态、应用更改,然后协调更新DOM。
editor.update() - 函数约定: 前缀为
$的函数(如$、$getRoot())必须在$getSelection()或editor.update()闭包内运行——类似React钩子需要组件上下文。editor.read() - 插件=React组件: 在React绑定中,插件是作为子组件渲染的React组件。它通过
<LexicalComposer>访问编辑器,并在useLexicalComposerContext()中注册命令/转换/监听器。useEffect - 命令驱动架构: 用户交互和插件通信通过带优先级监听器的类型化命令进行流转,使插件能够拦截或增强行为。
- 节点驱动的内容模型: 内容是类型化节点的树状结构。自定义内容类型(提及、嵌入内容、投票)通过自定义节点类实现。
何时使用Lexical:
- 需要自定义格式和嵌入内容的富文本编辑
- 需要结构化输出的内容编辑器(而非仅HTML字符串)
- 需要无障碍支持和屏幕阅读器适配的编辑器
- 需要服务端渲染或无头处理编辑器内容的应用
何时不使用Lexical:
- 简单文本输入(更简单、更轻量)
<textarea> - 仅需纯装饰效果而不影响文档内容的编辑器
- 需要稳定1.0 API的项目(Lexical处于1.0前版本,API可能会变化)
<patterns>
Core Patterns
核心模式
Pattern 1: React Editor Setup
模式1:React编辑器搭建
The minimal setup uses wrapping plugin components. Each plugin is a React component that accesses the editor via context.
LexicalComposertypescript
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
const EDITOR_NAMESPACE = "MyEditor";
const theme = {
paragraph: "editor-paragraph",
text: {
bold: "editor-text-bold",
italic: "editor-text-italic",
},
};
function onError(error: Error) {
console.error(error);
}
const initialConfig = {
namespace: EDITOR_NAMESPACE,
theme,
onError,
nodes: [], // Register custom nodes here
};
export function Editor() {
return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
</LexicalComposer>
);
}Why good: Plugins compose as children, initialConfig centralizes node registration and theming, error boundary catches update errors gracefully
See examples/core.md for the full setup with OnChangePlugin, AutoFocusPlugin, and custom plugins.
最简搭建方式是使用包裹插件组件。每个插件都是通过上下文访问编辑器的React组件。
LexicalComposertypescript
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
const EDITOR_NAMESPACE = "MyEditor";
const theme = {
paragraph: "editor-paragraph",
text: {
bold: "editor-text-bold",
italic: "editor-text-italic",
},
};
function onError(error: Error) {
console.error(error);
}
const initialConfig = {
namespace: EDITOR_NAMESPACE,
theme,
onError,
nodes: [], // 在此处注册自定义节点
};
export function Editor() {
return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
</LexicalComposer>
);
}优势: 插件以子组件形式组合,initialConfig集中管理节点注册和主题,错误边界可以优雅捕获更新错误
查看examples/core.md获取包含OnChangePlugin、AutoFocusPlugin和自定义插件的完整搭建示例。
Pattern 2: The $
-Function Convention and Update Lifecycle
$模式2:$
函数约定与更新生命周期
$All state reads and mutations use -prefixed functions inside (mutable) or (read-only) closures. This ensures state consistency and prevents stale reads.
$editor.update()editor.read()typescript
import { $getRoot, $createParagraphNode, $createTextNode } from "lexical";
// Writing: editor.update() clones state, applies changes, reconciles DOM
editor.update(() => {
const root = $getRoot();
const paragraph = $createParagraphNode();
const text = $createTextNode("Hello world");
paragraph.append(text);
root.append(paragraph);
});
// Reading: editor.read() provides safe read-only access
editor.read(() => {
const root = $getRoot();
const textContent = root.getTextContent();
});Why good: Immutable state model prevents race conditions, prefix signals context requirement (like React hooks), update batching minimizes DOM reconciliation
$Gotcha: -functions called outside update/read closures throw runtime errors. There is no compile-time check.
$所有状态读取和修改操作都需在(可修改)或(只读)闭包内使用前缀的函数。这确保了状态一致性,防止读取到过期状态。
editor.update()editor.read()$typescript
import { $getRoot, $createParagraphNode, $createTextNode } from "lexical";
// 写入操作:editor.update()克隆状态、应用更改、协调更新DOM
editor.update(() => {
const root = $getRoot();
const paragraph = $createParagraphNode();
const text = $createTextNode("Hello world");
paragraph.append(text);
root.append(paragraph);
});
// 读取操作:editor.read()提供安全的只读访问
editor.read(() => {
const root = $getRoot();
const textContent = root.getTextContent();
});优势: 不可变状态模型防止竞态条件,前缀标识上下文要求(类似React钩子),批量更新最小化DOM协调操作
$注意事项: 在update/read闭包外调用函数会触发运行时错误,且无编译时检查。
$Pattern 3: Command System
模式3:命令系统
Commands are the communication bus between plugins, toolbars, and the editor core. Create typed commands, dispatch them from UI, and register listeners with priorities.
typescript
import {
createCommand,
COMMAND_PRIORITY_EDITOR,
COMMAND_PRIORITY_LOW,
type LexicalCommand,
} from "lexical";
// Create a typed command
export const INSERT_IMAGE_COMMAND: LexicalCommand<{
src: string;
alt: string;
}> = createCommand("INSERT_IMAGE_COMMAND");
// Dispatch from toolbar or UI
editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
src: "/image.png",
alt: "Photo",
});Priority levels (higher number = runs first, can intercept):
| Priority | Value | Use case |
|---|---|---|
| 4 | Emergency overrides |
| 3 | Table navigation, critical plugins |
| 2 | Standard plugin behavior |
| 1 | Default for most plugins |
| 0 | Base editor behavior |
Return from a listener to stop propagation to lower-priority listeners.
trueSee examples/core.md for the full command registration pattern with cleanup.
命令是插件、工具栏和编辑器核心之间的通信总线。创建类型化命令,从UI调度命令,并注册带优先级的监听器。
typescript
import {
createCommand,
COMMAND_PRIORITY_EDITOR,
COMMAND_PRIORITY_LOW,
type LexicalCommand,
} from "lexical";
// 创建类型化命令
export const INSERT_IMAGE_COMMAND: LexicalCommand<{
src: string;
alt: string;
}> = createCommand("INSERT_IMAGE_COMMAND");
// 从工具栏或UI调度命令
editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
src: "/image.png",
alt: "Photo",
});优先级等级(数值越高越先执行,可拦截后续命令):
| 优先级 | 数值 | 适用场景 |
|---|---|---|
| 4 | 紧急覆盖操作 |
| 3 | 表格导航、关键插件 |
| 2 | 标准插件行为 |
| 1 | 大多数插件的默认优先级 |
| 0 | 编辑器基础行为 |
**返回**可终止命令向低优先级监听器传播。
true查看examples/core.md获取带清理函数的完整命令注册模式。
Pattern 4: Custom Plugins (React)
模式4:自定义插件(React)
A plugin is a React component that registers commands, transforms, or listeners via . Always return cleanup functions from .
useLexicalComposerContextuseEffecttypescript
import { useEffect } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import {
COMMAND_PRIORITY_LOW,
FORMAT_TEXT_COMMAND,
type TextFormatType,
} from "lexical";
export function ToolbarPlugin() {
const [editor] = useLexicalComposerContext();
const handleFormat = (format: TextFormatType) => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
};
return (
<div className="toolbar">
<button onClick={() => handleFormat("bold")} type="button">
Bold
</button>
<button onClick={() => handleFormat("italic")} type="button">
Italic
</button>
</div>
);
}Why good: Plugin accesses editor through context hook, dispatches built-in FORMAT_TEXT_COMMAND, renders null or UI as needed
See examples/core.md for plugins that register commands with useEffect cleanup.
插件是通过注册命令、转换或监听器的React组件。务必从中返回清理函数。
useLexicalComposerContextuseEffecttypescript
import { useEffect } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import {
COMMAND_PRIORITY_LOW,
FORMAT_TEXT_COMMAND,
type TextFormatType,
} from "lexical";
export function ToolbarPlugin() {
const [editor] = useLexicalComposerContext();
const handleFormat = (format: TextFormatType) => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
};
return (
<div className="toolbar">
<button onClick={() => handleFormat("bold")} type="button">
粗体
</button>
<button onClick={() => handleFormat("italic")} type="button">
斜体
</button>
</div>
);
}优势: 插件通过上下文钩子访问编辑器,调度内置的FORMAT_TEXT_COMMAND,可根据需要渲染空内容或UI
查看examples/core.md获取使用useEffect清理函数注册命令的插件示例。
Pattern 5: Node Transforms
模式5:节点转换
Transforms automatically mutate nodes when conditions are met. They run before DOM reconciliation, making them the most efficient way to react to content changes.
typescript
import { TextNode } from "lexical";
// Transform: auto-capitalize first letter of paragraphs
editor.registerNodeTransform(TextNode, (textNode) => {
const text = textNode.getTextContent();
// CRITICAL: Precondition prevents infinite loop
if (text.length > 0 && text[0] !== text[0].toUpperCase()) {
textNode.setTextContent(text[0].toUpperCase() + text.slice(1));
}
});Why preconditions matter: Without the check, marks the node dirty, re-triggering the transform infinitely.
setTextContentTransform execution order: Leaf nodes first, then element nodes, then RootNode. Multiple transforms produce a single DOM reconciliation.
See examples/core.md for transform registration with cleanup and use cases.
转换会在满足条件时自动修改节点。它们在DOM协调前运行,是响应内容变化最高效的方式。
typescript
import { TextNode } from "lexical";
// 转换:自动将段落首字母大写
editor.registerNodeTransform(TextNode, (textNode) => {
const text = textNode.getTextContent();
// 重要:前置条件防止无限循环
if (text.length > 0 && text[0] !== text[0].toUpperCase()) {
textNode.setTextContent(text[0].toUpperCase() + text.slice(1));
}
});前置条件的重要性: 若无此检查,会标记节点为脏状态,反复触发转换导致无限循环。
setTextContent转换执行顺序: 先叶子节点,再元素节点,最后RootNode。多个转换只会触发一次DOM协调。
查看examples/core.md获取带清理函数的转换注册及用例。
Pattern 6: Custom Nodes
模式6:自定义节点
Lexical provides three extendable base nodes for custom content types:
| Base Node | Purpose | Key method |
|---|---|---|
| Container nodes (blockquote, callout) | |
| Styled text variants (colored text, mentions) | |
| Embedded components (images, videos, polls) | |
Every custom node requires:
- -- unique string identifier
static getType() - -- create copy for state snapshots
static clone(node) - -- return the HTMLElement representation
createDOM() - -- return
updateDOM()if existing DOM can be reusedfalse - /
exportJSON()-- serializationstatic importJSON() - Registration in
initialConfig.nodes
typescript
import { DecoratorNode } from "lexical";
import type { LexicalNode, NodeKey, EditorConfig } from "lexical";
export class ImageNode extends DecoratorNode<JSX.Element> {
__src: string;
__alt: string;
static getType(): string {
return "image";
}
static clone(node: ImageNode): ImageNode {
return new ImageNode(node.__src, node.__alt, node.__key);
}
constructor(src: string, alt: string, key?: NodeKey) {
super(key);
this.__src = src;
this.__alt = alt;
}
createDOM(_config: EditorConfig): HTMLElement {
return document.createElement("div");
}
updateDOM(): boolean {
return false;
}
decorate(): JSX.Element {
return <img src={this.__src} alt={this.__alt} />;
}
}Property convention: Prefix private properties with (double underscore) to prevent minification issues. All properties must be JSON-serializable.
__See examples/custom-nodes.md for complete ElementNode, TextNode, DecoratorNode examples with serialization and the NodeState API.
Lexical提供三种可扩展的基础节点用于自定义内容类型:
| 基础节点 | 用途 | 核心方法 |
|---|---|---|
| 容器节点(块引用、提示框) | |
| 带样式的文本变体(彩色文本、提及) | |
| 嵌入组件(图片、视频、投票) | |
每个自定义节点需要:
- -- 唯一字符串标识符
static getType() - -- 为状态快照创建副本
static clone(node) - -- 返回HTMLElement表示
createDOM() - -- 若现有DOM可复用则返回
updateDOM()false - /
exportJSON()-- 序列化static importJSON() - 在中注册
initialConfig.nodes
typescript
import { DecoratorNode } from "lexical";
import type { LexicalNode, NodeKey, EditorConfig } from "lexical";
export class ImageNode extends DecoratorNode<JSX.Element> {
__src: string;
__alt: string;
static getType(): string {
return "image";
}
static clone(node: ImageNode): ImageNode {
return new ImageNode(node.__src, node.__alt, node.__key);
}
constructor(src: string, alt: string, key?: NodeKey) {
super(key);
this.__src = src;
this.__alt = alt;
}
createDOM(_config: EditorConfig): HTMLElement {
return document.createElement("div");
}
updateDOM(): boolean {
return false;
}
decorate(): JSX.Element {
return <img src={this.__src} alt={this.__alt} />;
}
}属性约定: 私有属性前缀为(双下划线)以防止压缩问题。所有属性必须可JSON序列化。
__查看examples/custom-nodes.md获取完整的ElementNode、TextNode、DecoratorNode示例,包含序列化和NodeState API。
Pattern 7: EditorState Serialization
模式7:EditorState序列化
Lexical supports JSON (preferred for persistence) and HTML (for display or interop).
typescript
import { $generateHtmlFromNodes } from "@lexical/html";
// JSON: lossless round-trip
const json = editor.getEditorState().toJSON();
const jsonString = JSON.stringify(json);
// Restore from JSON
const editorState = editor.parseEditorState(jsonString);
editor.setEditorState(editorState);
// HTML: for rendering or export
editor.read(() => {
const html = $generateHtmlFromNodes(editor, null);
});JSON vs HTML: JSON preserves the full node tree and is the recommended format for persistence. HTML is lossy (loses custom node properties) but useful for display or email content.
See examples/serialization.md for complete import/export patterns, HTML-to-Lexical conversion, and headless editor usage.
</patterns>
<decision_framework>
Lexical支持JSON(推荐用于持久化)和HTML(用于展示或互操作)两种格式。
typescript
import { $generateHtmlFromNodes } from "@lexical/html";
// JSON:无损往返序列化
const json = editor.getEditorState().toJSON();
const jsonString = JSON.stringify(json);
// 从JSON恢复
const editorState = editor.parseEditorState(jsonString);
editor.setEditorState(editorState);
// HTML:用于渲染或导出
editor.read(() => {
const html = $generateHtmlFromNodes(editor, null);
});JSON vs HTML: JSON保留完整节点树,是推荐的持久化格式。HTML是有损的(丢失自定义节点属性),但适用于展示或邮件内容。
查看examples/serialization.md获取完整的导入/导出模式、HTML转Lexical转换和无头编辑器使用方法。
</patterns>
<decision_framework>
Decision Framework
决策框架
Which Node Type to Extend
选择要扩展的节点类型
Does your content contain child nodes?
├─ YES → ElementNode (paragraphs, blockquotes, callouts)
└─ NO → Is it text with special formatting or behavior?
├─ YES → TextNode (colored text, mentions)
└─ NO → Is it an embedded component (image, video, widget)?
├─ YES → DecoratorNode (renders arbitrary UI)
└─ NO → Re-evaluate: most content fits one of the above你的内容包含子节点吗?
├─ 是 → ElementNode(段落、块引用、提示框)
└─ 否 → 是否是带有特殊格式或行为的文本?
├─ 是 → TextNode(彩色文本、提及)
└─ 否 → 是否是嵌入组件(图片、视频、小部件)?
├─ 是 → DecoratorNode(渲染任意UI)
└─ 否 → 重新评估:大多数内容都符合上述类型之一Plugin vs Transform vs Listener
插件 vs 转换 vs 监听器
Need to react to content changes?
├─ YES → Does the reaction modify nodes?
│ ├─ YES → Transform (most efficient, runs before DOM reconciliation)
│ └─ NO → Update listener (read-only, runs after reconciliation)
└─ NO → Need to handle user actions or toolbar clicks?
├─ YES → Command (typed, priority-based, interceptable)
└─ NO → Listener (registerUpdateListener for state observation)需要响应内容变化吗?
├─ 是 → 响应操作是否会修改节点?
│ ├─ 是 → 转换(最高效,在DOM协调前运行)
│ └─ 否 → 更新监听器(只读,在协调后运行)
└─ 否 → 需要处理用户操作或工具栏点击吗?
├─ 是 → 命令(类型化、基于优先级、可拦截)
└─ 否 → 监听器(注册updateListener观察状态)Command Priority Selection
命令优先级选择
Is this the base editor behavior?
├─ YES → COMMAND_PRIORITY_EDITOR (0)
└─ NO → Is this a standard plugin?
├─ YES → COMMAND_PRIORITY_LOW (1) or COMMAND_PRIORITY_NORMAL (2)
└─ NO → Must it override other plugins (e.g., table navigation)?
├─ YES → COMMAND_PRIORITY_HIGH (3)
└─ NO → Emergency override only?
└─ YES → COMMAND_PRIORITY_CRITICAL (4)</decision_framework>
<red_flags>
这是编辑器基础行为吗?
├─ 是 → COMMAND_PRIORITY_EDITOR (0)
└─ 否 → 这是标准插件吗?
├─ 是 → COMMAND_PRIORITY_LOW (1) 或 COMMAND_PRIORITY_NORMAL (2)
└─ 否 → 必须覆盖其他插件吗(如表格导航)?
├─ 是 → COMMAND_PRIORITY_HIGH (3)
└─ 否 → 仅用于紧急覆盖?
└─ 是 → COMMAND_PRIORITY_CRITICAL (4)</decision_framework>
<red_flags>
RED FLAGS
注意事项
High Priority Issues:
- Calling -prefixed functions outside
$oreditor.update()-- causes runtime errors with no compile-time warningeditor.read() - Missing node registration in -- custom nodes silently fail or throw when the editor encounters them
initialConfig.nodes - Transforms without preconditions -- unconditional mutations retrigger the transform infinitely, freezing the editor
- Using inside an update listener to modify state -- breaks undo/redo history and causes extra renders; use transforms instead
editor.update() - Forgetting cleanup for register calls -- leaks listeners, causes stale references after component unmount
useEffect
Medium Priority Issues:
- Direct DOM manipulation instead of using the node/command system -- bypasses the reconciler, causes state-DOM desync
- Storing non-JSON-serializable values in node properties (functions, Maps, Sets) -- breaks serialization silently
- Using without cloning -- can cause unexpected focus changes; use
editor.setEditorState()to prevent auto-focuseditorState.clone(null) - Naming custom node with a non-unique string -- collides with other nodes, causes deserialization failures
getType() - Using directly instead of
new MyNode()factory -- bypasses the node replacement system ($createMyNode())$applyNodeReplacement - Single underscore node properties (instead of
_value) -- may be mangled by minifiers, breaking node access__value - in
console.logcallback with no rethrow -- silently swallows editor errors; rethrow or send to error trackingonError
Gotchas & Edge Cases:
- batches synchronously but reconciles asynchronously -- use
editor.update()option when you need synchronous DOM commit (e.g., before reading DOM measurements){ discrete: true } - Node property names must use prefix convention -- single underscore properties may be mangled by minifiers
__ - returns a component that Lexical renders outside the normal React tree -- state management in decorator components needs care
DecoratorNode.decorate() - The callback in
onErrorreceives errors from update closures -- if you don't rethrow, Lexical tries to recover gracefullyinitialConfig - modes:
TextNodemakes text immutable (like a chip),"token"deletes word-by-word"segmented" - CSS does not work for animations based on node removal -- Lexical reconciles by removing DOM nodes, not hiding them
transition - The NodeState API (v0.26+) is experimental -- APIs may change without extended deprecation
</red_flags>
<critical_reminders>
高优先级问题:
- 在或
editor.update()外部调用editor.read()前缀函数——会触发无编译时警告的运行时错误$ - 未在中注册节点——自定义节点会静默失败或在编辑器遇到时抛出错误
initialConfig.nodes - 转换无前置条件——无条件修改会反复触发转换,导致编辑器冻结
- 在更新监听器内使用修改状态——破坏撤销/重做历史,导致额外渲染;应使用转换代替
editor.update() - 注册调用时忘记清理——泄漏监听器,组件卸载后导致过期引用
useEffect
中优先级问题:
- 直接操作DOM而非使用节点/命令系统——绕过协调器,导致状态与DOM不一致
- 在节点属性中存储不可JSON序列化的值(函数、Map、Set)——静默破坏序列化
- 不克隆就使用——可能导致意外的焦点变化;使用
editor.setEditorState()防止自动聚焦editorState.clone(null) - 自定义节点使用非唯一字符串——与其他节点冲突,导致反序列化失败
getType() - 直接使用而非
new MyNode()工厂函数——绕过节点替换系统($createMyNode())$applyNodeReplacement - 节点属性使用单下划线(而非
_value)——可能被压缩器混淆,破坏节点访问__value - 回调中仅用
onError而不重新抛出——静默吞没编辑器错误;应重新抛出或发送到错误跟踪系统console.log
注意事项与边缘情况:
- 同步批量处理但异步协调DOM——当需要同步提交DOM时(如读取DOM尺寸前)使用
editor.update()选项{ discrete: true } - 节点属性名称必须使用前缀约定——单下划线属性可能被压缩器混淆
__ - 返回的组件由Lexical在正常React树外部渲染——装饰组件的状态管理需要注意
DecoratorNode.decorate() - 中的
initialConfig回调接收update闭包中的错误——若不重新抛出,Lexical会尝试优雅恢复onError - TextNode模式:使文本不可变(类似芯片),
"token"支持逐词删除"segmented" - 基于节点移除的动画无法使用CSS ——Lexical通过移除DOM节点进行协调,而非隐藏
transition - NodeState API(v0.26+)处于实验阶段——API可能会在无长期弃用通知的情况下更改
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md
(You MUST call -prefixed functions (, , ) ONLY inside or closures -- calling them outside throws runtime errors)
$$getRoot$getSelection$createTextNodeeditor.update()editor.read()(You MUST register custom nodes in the array of -- unregistered nodes cause silent failures or runtime errors)
nodesinitialConfig(You MUST return a cleanup function from when registering commands, transforms, or listeners -- Lexical register methods return unsubscribe functions)
useEffect(You MUST include preconditions in transforms to prevent infinite loops -- a transform that unconditionally modifies its target node re-triggers itself)
Failure to follow these rules will cause runtime errors, memory leaks, frozen editors, and broken undo/redo history.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定
(你必须仅在或闭包内调用前缀的函数(如、、)——在外部调用会触发运行时错误)
editor.update()editor.read()$$getRoot$getSelection$createTextNode(你必须在的数组中注册自定义节点——未注册的节点会导致静默失败或运行时错误)
initialConfignodes(注册命令、转换或监听器时,必须从中返回清理函数——Lexical的注册方法会返回取消订阅函数)
useEffect(转换中必须包含前置条件以防止无限循环——无条件修改目标节点的转换会反复触发自身)
不遵循这些规则会导致运行时错误、内存泄漏、编辑器冻结和撤销/重做历史损坏。
</critical_reminders>