Lexical 只读模式(Read Mode)与编辑模式(Edit Mode)完整指南
【免费下载链接】lexicalLexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.项目地址: https://gitcode.com/GitHub_Trending/le/lexical
导读
本文围绕 Lexical 编辑器的两种工作模式——只读模式(Read Mode)与编辑模式(Edit Mode)——展开。无论你是在构建文档预览、评论展示、聊天记录回放,还是需要临时锁定编辑器内容防止误编辑,掌握editable配置与setEditable()/isEditable()/registerEditableListener()这套 API 都是基础中的基础。读完本文,你将能够:在编辑器创建时或运行期任意切换只读/编辑模式、精确读取当前模式、监听模式切换事件驱动 UI 联动,并理解其底层基于contentEditable的实现原理与事件拦截机制。
1. 什么是 Read Mode / Edit Mode
Lexical 支持两种模式:
- Read mode(只读模式):
editable为false,编辑器不再接受用户输入,适合预览、回放、审核等场景; - Edit mode(编辑模式):
editable为true,编辑器正常接收输入,是 Lexical 的默认行为。
文档原文明确指出:"The default behavior for Lexical is edit mode, or more accurately not read only mode."换句话说,默认值就是可编辑,无需任何显式配置。
从底层实现看,两种模式的本质区别在于根 DOM 元素(contentEditable)上contenteditable属性的设置。在 packages/lexical/src/LexicalUtils.ts 中可以看到,Lexical 会根据编辑器的可编辑状态把元素的contentEditable设为'true'或'false'(相关实现位于 LexicalUtils.ts 的contentEditable赋值逻辑附近)。这是浏览器原生能力与 Lexical 模式系统的衔接点:只读时浏览器本身就拒绝一切键盘/鼠标文本编辑,从而在根源上杜绝了用户改动内容。
模式切换对插件同样可见:特定插件可以监听模式变化(详见下文registerEditableListener),根据模式自定义部分 UI——例如只读时隐藏工具栏按钮、切换为"阅读视图"样式,或在编辑模式下才显示拖拽手柄。
2. 设置模式:创建时配置与运行期切换
2.1 在创建编辑器时设置
使用核心包lexical时,在createEditor的配置对象中传入editable:
import {createEditor} from 'lexical'; const editor = createEditor({ editable: true, // ...其余配置:nodes、theme、onError 等 });editable是一个可选布尔值。查看 createEditor 的实现可以发现默认值的处理逻辑:
const isEditable = config.editable !== undefined ? config.editable : true;也就是说,只要不传editable,默认即为true(编辑模式)。该值随后在编辑器构造时被存入内部字段this._editable = editable(见 LexicalEditor.ts),并贯穿整个编辑器生命周期。
2.2 在 React 中使用<LexicalComposer>设置
如果你使用@lexical/react,模式是在传给<LexicalComposer>的initialConfig中配置的:
import {LexicalComposer} from '@lexical/react/LexicalComposer'; <LexicalComposer initialConfig={{editable: true}}> {/* 在这里放置 RichTextPlugin、ContentEditable、工具栏等 */} </LexicalComposer>在 LexicalComposer 源码 中可以看到完整的传递链路:
initialConfig被传入createEditor({editable: initialConfig.editable, ...})(LexicalComposer.tsx);- 在
useLayoutEffect中执行editor.setEditable(isEditable !== undefined ? isEditable : true)(LexicalComposer.tsx),确保挂载后编辑器的实际模式与配置一致。
因此,即使你漏配editable,React 封装也会帮你兜底为true。
initialConfig的类型定义(LexicalComposer.tsx)中,editable被明确注释为"initial editable state",即仅在编辑器创建时读取一次。想修改模式,请走下面的命令式 API。
2.3 创建之后命令式切换模式
编辑器创建后,可以在任意时刻通过editor.setEditable()命令式切换:
// 切换到只读 editor.setEditable(false); // 恢复可编辑 editor.setEditable(true);底层实现在 LexicalEditor.ts:
setEditable(editable: boolean): void { if (this._editable !== editable) { this._editable = editable; triggerListeners('editable', this, true, editable); // ... } }值得注意的实现细节:
- 去重保护:只有当新值与当前值不同时才真正触发变更,重复设置相同值不会产生多余事件;
- 主动通知:变更后立即触发
editable监听器(即registerEditableListener注册的回调); - DOM 联动:若编辑器使用了 named-slot 等机制,还会触发一次 reconcile 更新,把只读状态同步到相关 DOM 元素上(见 LexicalEditor.ts 的注释与
$fullReconcile()调用)。
提示:
setEditable是同步方法,调用后立刻调用isEditable()即可读到新值,无需等待下一次更新提交。
3. 读取模式:isEditable 与监听器
3.1 查询当前模式
使用editor.isEditable()获取当前编辑器的可编辑状态:
const isEditable = editor.isEditable(); // true 或 false实现非常直接(LexicalEditor.ts):
isEditable(): boolean { return this._editable; }3.2 监听模式变化:registerEditableListener
如果你需要在模式切换时得到通知(例如隐藏/显示工具栏、切换只读样式),可以注册一个可编辑状态监听器:
const removeEditableListener = editor.registerEditableListener( (isEditable) => { // 回调参数即当前模式 console.log(isEditable); }, ); // 不再需要时务必注销,防止内存泄漏 removeEditableListener();registerEditableListener的实现(LexicalEditor.ts)将回调注册到内部的editable监听器集合,并返回一个销毁函数(teardown function),调用它即可在组件卸载或不再需要时解除监听。这一点在文档中也被特别强调:"Do not forget to unregister the listener when no longer needed!"
结合setEditable的源码可以推断出完整行为闭环:
- 调用
setEditable(newValue); - 内部比对发现值变化;
triggerListeners('editable', this, true, editable)将所有已注册回调以新布尔值调用;- 返回值
removeEditableListener()用于事后清理。
3.3 单元测试佐证
仓库的单元测试 packages/lexical/src/tests/unit/LexicalEditor.test.tsx 对该行为有明确的验证(editable listener用例,位于文件 LexicalEditor.test.tsx 中):
const editableFn = vi.fn(); editor.registerEditableListener(editableFn); expect(editor.isEditable()).toBe(true); // 默认可编辑 editor.setEditable(false); expect(editor.isEditable()).toBe(false); // 切换为只读 editor.setEditable(true); expect(editableFn.mock.calls).toEqual([[false], [true]]); // 回调依次收到 false、true该测试证实了三件事:默认模式为可编辑、setEditable后isEditable立即反映新值、监听器在每次有效切换时被触发且参数为最新布尔值。此外,测试中还有setEditable与根元素相关的用例(例如 "Retains pendingEditor while rootNode is not set" 遍历[true, false]两种模式),覆盖了只读/可编辑两种模式下的状态一致性场景。
4. 在 React 中订阅模式:useLexicalEditable
如果你使用@lexical/react且需要响应式地读取模式,官方推荐使用useLexicalEditable()Hook,而不是手动注册监听器。其实现位于 packages/lexical-react/src/useLexicalEditable.ts:
function subscription(editor: LexicalEditor): LexicalSubscription<boolean> { return { initialValueFn: () => editor.isEditable(), subscribe: callback => { return editor.registerEditableListener(callback); }, }; } export function useLexicalEditable(): boolean { return useLexicalSubscription(subscription); }该 Hook 在内部使用useLexicalSubscription完成订阅,在组件中直接返回当前的isEditable布尔值,并在模式变化时触发重渲染。其 JSDoc 注释明确说明:手动用registerEditableListener观察该值"比较棘手,尤其是在 React StrictMode(开发环境默认开启)或并发模式下",因此建议优先使用本 Hook。
仓库内部的实践佐证——以下插件均通过useLexicalEditable()响应式切换 UI:
- LexicalRichTextPlugin.tsx 与 LexicalPlainTextPlugin.tsx:根据
editable决定是否渲染输入相关元素; - LexicalDraggableBlockPlugin.tsx:根据
useLexicalEditable()决定拖拽手柄的渲染与更新(注释还特别提到该 Hook 能正确处理 StrictMode 场景)。
典型用法:
import {useLexicalEditable} from '@lexical/react/useLexicalEditable'; function Toolbar() { const isEditable = useLexicalEditable(); return ( <div> {isEditable ? ( <button onClick={/* 加粗 */}>B</button> ) : ( <span>只读模式,工具栏已隐藏</span> )} </div> ); }5. 底层原理:contentEditable 与事件拦截
文档指出,模式的底层实现细节是contentEditable被设为"false"或"true"。结合源码可以进一步确认两点:
第一,事件层面的拦截。在 packages/lexical/src/LexicalEvents.ts 中,大量原生事件(mousedown、keydown、beforeinput、composition 等)的处理器都会先检查editor.isEditable(),例如:
if (editor.isEditable() || eventName === 'click') { // 只有可编辑时才处理输入类事件;click 例外,仍允许处理 }这意味着:即便某些浏览器行为绕过contenteditable=false,Lexical 的事件系统也会在内部再次把关,双保险地保证只读模式下的内容安全。其中click事件在只读时依然会被处理,从而保证选中、光标定位、链接点击等非破坏性交互可用。
第二,DOM 属性同步。LexicalUtils.ts 中处理 named-slot 等可编辑"孤岛"时,会根据editor.isEditable()显式设置element.contentEditable = editable ? 'true' : 'false',确保内嵌的编辑器子树与主编辑器的只读状态保持一致。
6. 实战场景与最佳实践
6.1 常见应用场景
- 文档预览 / 详情页:展示已保存的富文本内容,
editable: false,配合自定义只读样式; - 评论与聊天记录:历史消息只读、输入框可编辑,二者可共存于同一页面;
- 权限控制:根据用户角色在编辑器创建时决定是否允许编辑;
- 表单锁定:编辑中提交后
setEditable(false)锁定,防止再次修改; - 回放 / 演示:定时切换模式,模拟"用户正在输入"的演示效果。
6.2 实践建议
- 默认值不用显式写:需要可编辑时不写
editable即可(默认为true),代码更简洁; - 创建后切换走命令式 API:
initialConfig.editable只在创建时生效,动态切换请用editor.setEditable(); - React 组件内优先用
useLexicalEditable():可避免 StrictMode 下的订阅问题,并自动触发重渲染; - 务必注销监听器:手动
registerEditableListener时保留返回的 teardown 函数,在组件卸载或作用域结束时调用,防止内存泄漏; - 只读模式依然可交互:
click等非破坏性事件在只读模式下仍被处理,可放心依赖选中、聚焦等行为; - 结合主题与插件联动:模式变化事件可驱动工具栏显隐、只读样式切换,参考
LexicalDraggableBlockPlugin的做法。
7. 小结
Lexical 的 Read Mode / Edit Mode 是一套简洁而完整的模式系统:
| 能力 | API | 说明 |
|---|---|---|
| 创建时配置 | createEditor({editable})/<LexicalComposer initialConfig={{editable}}> | 默认true,仅在创建时读取 |
| 运行期切换 | editor.setEditable(boolean) | 同步生效,值未变化时不触发事件 |
| 查询当前模式 | editor.isEditable() | 返回当前布尔值 |
| 监听模式变化 | editor.registerEditableListener(cb) | 返回 teardown 函数用于注销 |
| React 响应式订阅 | useLexicalEditable() | 基于useLexicalSubscription,推荐在组件内使用 |
从源码层面看,模式的本质是contentEditable属性的 true/false 切换,辅以 LexicalEvents.ts 中的事件级双重拦截,确保只读模式真正"读不可写";而监听器机制(registerEditableListener)让插件与 UI 能够随模式变化实时联动,这也正是 Lexical 可扩展性的体现之一。
相关源码索引
- 编辑器核心与
setEditable/isEditable/registerEditableListener实现:packages/lexical/src/LexicalEditor.ts createEditor及editable默认值处理:packages/lexical/src/LexicalEditor.ts- 事件层
isEditable拦截:packages/lexical/src/LexicalEvents.ts - React 封装与
initialConfig.editable传递:packages/lexical-react/src/LexicalComposer.tsx - 响应式订阅 Hook:packages/lexical-react/src/useLexicalEditable.ts
- 单元测试验证:packages/lexical/src/tests/unit/LexicalEditor.test.tsx
【免费下载链接】lexicalLexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.项目地址: https://gitcode.com/GitHub_Trending/le/lexical
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考