slate源码解析(一):总体概览

794 阅读4分钟

本文是根据 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 是一个对象,包含 pathoffset 两个属性。

通常一个 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

  1. Transforms.insertNodes(editor, nodes, options): 在编辑器的当前选区插入节点,如果选区是扩展的,那么会替换选区中的内容。

  2. Transforms.removeNodes(editor, options): 删除选区中的节点。

  3. Transforms.moveNodes(editor, options): 移动节点到新的位置。

  4. Transforms.wrapNodes(editor, element, options): 将选区中的节点包裹进一个新的父节点中。

  5. Transforms.unwrapNodes(editor, options): 移除选区中的节点的父节点。

  6. Transforms.collapse(editor, options): 折叠编辑器的选区到一个点。

  7. Transforms.setNodes(editor, properties, options): 设置选区中的节点的属性。

这些 Transforms 提供了编辑Slate编辑器状态的强大能力,它们可以适应复杂的文本编辑需求,如插入、删除、修改文本,更改格式,添加超链接等。

Node

Slate中的Node就是表示文档中的节点,也就是渲染出来的数据结构。

一个 Node 可以是一个 EditorElementText

比如例子中的 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,其中方法大致分为以下几种类型:

  1. 获取特定类型的节点:像 ancestor, descendant, child, parent, leaf, common, get 这样的方法都是用来获取某种特定类型的节点。

  2. 获取节点的迭代器:像 ancestors, children, descendants, elements, levels, nodes, texts 这样的方法返回的是 Generator 对象,可以用来迭代特定类型的节点。

  3. 节点属性和比较:像 extractProps, matches, isNode, isNodeList 这样的方法用于获取节点的属性,或者比较节点和某个属性对象。

  4. 节点路径和范围操作:像 fragment, first, last, has 这样的方法主要用于处理节点路径和节点范围。

  5. 节点文本内容string 方法用于获取节点的文本内容。