本文是根据 slate 当前版本 0.94.1 做的解析。先从目录结构和一些重要数据结构去做一个大体的介绍。再抓其中几个细节点的实现去探究。
slate目录结构
slate源代码仓库使用了 monorepo 的管理方式,各个包存于 /packages 文件下:
├── slate/ # 核心 slate 包
├── slate-history/ # 提供历史记录功能的包
├── slate-hyperscript/ # 提供虚拟 DOM 功能的包
├── slate-react/ # 提供 React 绑定的包
采用数据处理和模型映射分离的方案,所以分开 /slate和 /slate-react 。因此也可以使用slate这个核心库去实现支持 vue、angular 之类的功能(社区有相关的开源方案)。
/slate 下面的文件夹:
├── editor/ # 提供操作编辑器的核心方法和判断工具
├── interfaces/ # 定义 TS 中的 interfaces 集合文件
├── transforms-node/ # Transforms是slate编辑器中进行操作模型的方法,比如添加、删除、移动节点
├── transforms-selection/ # 同上,用来操作选区
├── transforms-text/ # 同上,用来操作文本节点
├── types/ # 定义 TS 中的 type 集合文件
├── utils/ # 一些公用的工具库
├── create-editor.ts # 创建编辑器
/slate-react 下面的文件夹:
├── components/ # React组件,如Editable,Slate等
├── hooks/ # 自定义的React Hooks
├── plugin/ # 提供插件相关功能的文件
├── utils/ # 工具函数
核心概念
Selection、Path、Point
在 Slate 中,Selection 是一种用来描述用户在文档中选中的文本范围的数据结构,Path 是一个由数字组成的数组,表示在 Slate 树形数据结构中的位置,而 Point 是一个对象,包含 path 和 offset 两个属性。
通常一个 Selection 的数据结构如下:
{
// 这是一个Point结构
"anchor": {
"path": [0, 0], // 这是一个Path结构
"offset": 0
},
"focus": {
"path": [0, 5],
"offset": 0
}
}
在 Slate 的实际使用中,可以使用 Transforms 中的一些方法来操作 Selection,例如 Transforms.setSelection 可以用来设置当前的选区。
Transforms
Slate库中的 Transforms 是一系列操作,可以用来改变Slate的编辑器状态,包括文档内容、选区等。
下面是一些常用的 Transforms:
-
Transforms.insertNodes(editor, nodes, options): 在编辑器的当前选区插入节点,如果选区是扩展的,那么会替换选区中的内容。 -
Transforms.removeNodes(editor, options): 删除选区中的节点。 -
Transforms.moveNodes(editor, options): 移动节点到新的位置。 -
Transforms.wrapNodes(editor, element, options): 将选区中的节点包裹进一个新的父节点中。 -
Transforms.unwrapNodes(editor, options): 移除选区中的节点的父节点。 -
Transforms.collapse(editor, options): 折叠编辑器的选区到一个点。 -
Transforms.setNodes(editor, properties, options): 设置选区中的节点的属性。
这些 Transforms 提供了编辑Slate编辑器状态的强大能力,它们可以适应复杂的文本编辑需求,如插入、删除、修改文本,更改格式,添加超链接等。
Node
Slate中的Node就是表示文档中的节点,也就是渲染出来的数据结构。
一个 Node 可以是一个 Editor、Element 或 Text。
比如例子中的 initValue:
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{ text: 'This is editable ' },
{ text: 'rich', bold: true },
{ text: ' text, ' },
{ text: 'much', italic: true },
{ text: ' better than a ' },
{ text: '<textarea>', code: true },
{ text: '!' },
],
},
{
type: 'paragraph',
children: [
{
text:
"Since it's rich text, you can do things like turn a selection of text ",
},
{ text: 'bold', bold: true },
{
text:
', or add a semantically rendered block quote in the middle of the page, like this:',
},
],
},
{
type: 'block-quote',
children: [{ text: 'A wise quote.' }],
},
{
type: 'paragraph',
align: 'center',
children: [{ text: 'Try it out for yourself!' }],
},
]
所对应的展示视图:
并且Slate提供了一系列方法,用于查找和操作 Node 对象。
文件路径在:/slate/src/interfaces/node.ts,其中方法大致分为以下几种类型:
-
获取特定类型的节点:像
ancestor,descendant,child,parent,leaf,common,get这样的方法都是用来获取某种特定类型的节点。 -
获取节点的迭代器:像
ancestors,children,descendants,elements,levels,nodes,texts这样的方法返回的是 Generator 对象,可以用来迭代特定类型的节点。 -
节点属性和比较:像
extractProps,matches,isNode,isNodeList这样的方法用于获取节点的属性,或者比较节点和某个属性对象。 -
节点路径和范围操作:像
fragment,first,last,has这样的方法主要用于处理节点路径和节点范围。 -
节点文本内容:
string方法用于获取节点的文本内容。