Lexical
⚠️ Lexical 目前处于早期开发阶段,API 和包可能会经常更改.
有关 Lexical 的文档和更多信息,请务必访问 Lexical 网站。 示例:
和React一起使用
注意:不仅限于 React。可以支持任何基于 DOM 的底层数据绑定库。
安装 lexical
和@lexical/react
:
npm install --save lexical @lexical/react
下面是一个基本的纯文本编辑器示例,使用 lexical
和 @lexical/react
(在线沙盒).
import {$getRoot, $getSelection} from 'lexical';
import {useEffect} from 'react';
import LexicalComposer from '@lexical/react/LexicalComposer';
import LexicalPlainTextPlugin from '@lexical/react/LexicalPlainTextPlugin';
import LexicalContentEditable from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import LexicalOnChangePlugin from '@lexical/react/LexicalOnChangePlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
const theme = {
// 主题样式放在这里
...
}
// 当编辑器变化时,你可以通过LexicalOnChangePlugin组件收到通知
// LexicalOnChangePlugin!
function onChange(editorState) {
editorState.read(() => {
// Read the contents of the EditorState here.
const root = $getRoot();
const selection = $getSelection();
console.log(root, selection);
});
}
// 插件(plugins)就是React组件, 可以灵活组合
// 另外,你也可以使用懒加载,直到用的时候才产生开销
function MyCustomAutoFocusPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// 编辑器聚焦
editor.focus();
}, [editor]);
return null;
}
// 在编辑器更新过程中,捕获错误
// 如果不捕获错误,Lexical会试图恢复,而不会丢失数据
function onError(error) {
console.error(error);
}
function Editor() {
const initialConfig = {
theme,
onError,
};
return (
<LexicalComposer initialConfig={initialConfig}>
<LexicalPlainTextPlugin
contentEditable={<LexicalContentEditable />}
placeholder={<div>Enter some text...</div>}
/>
<LexicalOnChangePlugin onChange={onChange} />
<HistoryPlugin />
<MyCustomAutoFocusPlugin />
</LexicalComposer>
);
}
核心概念
实例(Editor instances )
编辑器实例是连接一切的核心。您可以将 contenteditable DOM 元素附加进编辑器实例,还可以注册侦听器(**listeners **)和命令(commands)。最重要的是,编辑器允许更新状态(EditorState). 您可以使用 createEditor()
创建实例,一般情况下,使用像@lexical/react
的框架时不用担心太多问题。
状态(Editor States)
状态是表示需要显示的内容的底层数据模型。它包含两部分:
- 节点树 node tree
- 选择对象 selection object
编辑器状态一旦创建就不可更改,触发更新可以使用 editor.update(() => {...})
, 您也可以使用节点转换(node transforms)或命令(command)钩子更新——它们会在现有更新工作流的一部分被调用,并防止复杂更新(级联/瀑布)。另外,可以使用editor.getEditorState()
获取状态。
编辑器状态可以通过 editor.parseEditorState()
转为 JSON,方便保存或传输
更新(Editor Updates)
想要更改内容时,必须通过更新来完成,比如editor.update(() => {...})
,这个闭包回调很重要。这里是拥有完整的编辑器状态上下文的地方,并且可以访问到所有状态节点树。推荐在这种情况下使用$
作为函数名前缀,比较醒目。需要注意的是如果在这个更新回调之外,使用这些函数会报错。对于熟悉 React Hooks 的人,你可以认为它们是类似的(除了$
函数可以按任意顺序使用)。
协调器 (DOM Reconciler)
Lexical 有自己的 DOM 协调器,它包含一组编辑器状态(“current”和“pending”)。通过对比差异,只修改差异部分。您可以将其视为一种虚拟 DOM,性能优化,并且能够自动确保 LTR 和 RTL 语言的一致(有些语言是左对齐和右对齐的)。
监听器(Listeners), 节点转换(Node Transforms )和命令( Commands)
除了调用更新editor.update(() => {...})
之外,其实大部分工作是通过侦听器、节点转换和命令完成的。但使用前需要注册(register),并且所有注册方法都返回一个函数用来方便取消注册。
下面是更新监听器的例子
const unregisterListener = editor.registerUpdateListener(({editorState}) => {
// 更新发生了!
console.log(editorState);
});
// 随时可以移除注册的监听器
unregisterListener();
命令是用于和内容通信的。可以使用createCommand()
创建自定义命令,并使用editor.dispatchCommand(command, payload)
执行。比如当按键被触发或其他情况出现时,内部可以执行命令。命令通过editor.registerCommand(handler, priority)
注册,priority代表优先级。命令按优先级传播,直到处理完成(类似浏览器中的事件传播方式)。
开始使用
在React 中使用 Lexical ,建议 查看[@lexical/react](https://github.com/facebook/lexical/tree/main/packages/lexical-react/src)
.
创建编辑器
创建编辑器实例,并接受一个允许主题(theme)和其他可选配置对象:
import {createEditor} from 'lexical';
const config = {
theme: {
...
},
};
const editor = createEditor(config);
然后将实例与DOM元素关联:
const contentEditableElement = document.getElementById('editor');
editor.setRootElement(contentEditableElement);
如果要清除编辑器实例,可以设置setRootElement(null)
. 或者如果您需要切换到另一个元素setRootElement(newElement)
.
更新编辑器状态
对于 Lexical,数据源不是DOM,而是底层状态模型。您可以通过调用editor.getEditorState()
获取最新的编辑器状态。
编辑器状态可转为 JSON,并且还提供editor.parseEditorState
把JSON字符串直接转为编辑器状态。
const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());
const newEditorState = editor.parseEditorState(stringifiedEditorState);
更新编辑器
4种方法更新实例:
- 通过
editor.update()
触发更新 - 通过
editor.setEditorState()
设置编辑器状态 - 通过
editor.registerNodeTransform()
更新 - 通过
editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority)
命令
最常用的是editor.update()
, 需要传一个回调函数来修改状态。开始更新时,当前状态会被被克隆并用作起点。从技术角度来看,这意味着 L在更新期间使用了一种称为双缓冲的技术。有一个当前内容的状态,还有另一个正在更新的未来状态。
创建更新通常是一个异步过程,它允许 Lexical 在一次更新中批量处理多个更新——从而提高性能。当 Lexical 准备好更新 DOM 时,正在更新中的底层状态将形成一个新的不可修改的状态。但你可以随时调用editor.getEditorState()
返回最新的编辑器状态。
以下是如何更新实例的示例:
import {$getRoot, $getSelection, $createParagraphNode} from 'lexical';
// 在`editor.update`中,可以使用 $前缀的辅助函数。
// 这些函数不能在这个闭包之外使用,会出错。
//(如果你熟悉 React,就像在 React 函数组件之外使用钩子)。
editor.update(() => {
// 获取根节点
const root = $getRoot();
// 获取光标和选择
const selection = $getSelection();
// 创建段落
const paragraphNode = $createParagraphNode();
// 创建文本
const textNode = $createTextNode('Hello world');
// 把文本添加到段落
paragraphNode.append(textNode);
// 最后,添加到根节点
root.append(paragraphNode);
});
如果您想知道编辑器何时更新以便对更改做出反应,可以向编辑器添加更新侦听器,如下所示:
editor.registerUpdateListener(({editorState}) => {
// 读取最新的 EditorState 内容
editorState.read(() => {
// 和 editor.update() 一样,可以使用以 $ 为前缀的辅助函数
});
});
创建自定义节点
本地调试
- 克隆此仓库
- 安装依赖
npm install
- 启动本地服务器并运行测试
npm run start
npm run test-e2e:chromium
仅运行 e2e 测试- 这个服务需要为 e2e 测试运行
npm run start
将同时启动开发服务器和协作服务器。
如果您不需要协作,使用npm run dev
仅启动开发服务器.
可选但推荐,使用 VSCode 进行开发
- 下载并安装 VSCode
- 下载 (建议使用未修改的版本)
- 安装扩展
- 流(Flow )语言支持
- Make sure to follow the setup steps in the README
- Prettier
- 将 prettier 设置为默认格式化
editor.defaultFormatter
- 自动保存格式化
editor.formatOnSave
- 将 prettier 设置为默认格式化
- ESlint
文档
浏览器支持
- Firefox 52+
- Chrome 49+
- Edge 79+ (when Edge switched to Chromium)
- Safari 11+
- iOS 11+ (Safari)
- iPad OS 13+ (Safari)
- Android Chrome 72+
注意:Lexical 不支持 Internet Explorer 或 Edge 的旧版本。
贡献
- Create a new branch
git checkout -b my-new-branch
- Commit your changes
git commit -a -m 'Description of the changes'
- There are many ways of doing this and this is just a suggestion
- Push your branch to GitHub
git push origin my-new-branch
- Go to the repository page in GitHub and click on "Compare & pull request"
- The GitHub CLI allows you to skip the web interface for this step (and much more)