# 节点: 编辑器,元素和文本

最重要的类型是 Node 对象:

  • 包含整个文档内容的 Editor 根节点。
  • 在自定义域中拥有语义的 Element 容器节点。
  • 包含文档文本的 Text 叶子节点。

这三个接口组合在一起形成一棵树 -- 就像 DOM 一样。举例来说,这是一个简单的纯文本值:

const editor = {
  children: [
    {
      type: 'paragraph',
      children: [
        {
          text: 'A line of text!',
        },
      ],
    },
  ],
  // ...编辑器还有被省略的其他属性。
}
1
2
3
4
5
6
7
8
9
10
11
12
13

尽可能地对应 DOMSlate 的原则之一。人们总是使用 DOM 来描述类似富文本结构的文档。对应 DOM 有助于新用户熟悉类库,并且可以让我们重用经过重重考验的结构模式,而不是自己造一个新的轮子。

🤖 下面来自于 MDN Web 文档 的内容可以帮助你理解更多相应的 DOM概念:

Slate 文档是一个嵌套递归的结构。在文档中,元素可以有子节点 — 所有元素都可以无限制地拥有子节点。嵌套递归的结构确保你可以建模简单的行为,比如用户的 @ 提及 和 # 标签或是带标题的表格和图片。

# 编辑器(Editor)

Slate 的顶级节点就是 Editor 。它封装了文档的所有富文本内容。它的接口是这样的:

interface Editor {
  children: Node[]
  ...
}
1
2
3
4

我们稍后会介绍他的功能,但是对于节点来说最重要的部分是它的 children 属性,其中包含一个 Node 对象树。

# 元素(Element)

元素组成了富文本文档的中间层。它们是对你的域定制的节点。它们的接口是这样的:

interface Element {
  children: Node[]
  [key: string]: unknown
}
1
2
3
4

你可以为任何类型的内容定义自定义元素。比如你可能想要一个段落和引用在你的数据模型中,它们通过 type 属性区分:

const paragraph = {
  type: 'paragraph',
  children: [...],
}

const quote = {
  type: 'quote',
  children: [...],
}
1
2
3
4
5
6
7
8
9

需要提醒的是,你可以使用任何的自定义属性。在这个例子中, Slate 并不关心 type 属性具体是什么。如果你自定义了 link 节点,你可能有一个 url 属性:

const link = {
  type: 'link',
  url: 'https://example.com',
  children: [...],
}
1
2
3
4
5

或者你可能给所有的节点定义一个 ID 属性:

const paragraph = {
  id: 1,
  type: 'paragraph',
  children: [...],
}
1
2
3
4
5

重要的是元素总是有一个 children 属性。

# 块(Blocks) vs. 行内(Inlines)

根据你的用例,你可能想要为 Element 定义一个另一个行为,这个行为决定了它的编辑流。

所有元素默认是块 (block) 元素。它们被垂直空间隔开,并且永不重叠。

但是在某些情况下,比如链接,你可能想要把它作为行内 (Inlines) 元素流。 这样的话,它就会和文本节点位于同一级别和同样的流。

🤖 这个概念借用了 DOM 的行为,参考 块级元素 (opens new window)行内元素 (opens new window)

可以通过重写 editor.isInline 函数来定义哪些节点属于行内节点。(默认情况下,它总是返回 false。) 请注意,内联节点不能是父块的第一个或最后一个子节点,也不能在子节点数组中的另一个内联节点旁边。Slate 默认情况下会使用 normalizeNode 自动将 { text: '' } 子元素分隔开。

元素可以将块元素作为子元素包含。或者混合行内元素和文本元素,把它们作为子节点。但是元素不能同时包含行内元素和块元素。

# 空元素(Voids)

类似于块元素和行内元素,另一个你可以定义的特殊元素为空元素:它们的 void 性:

元素默认是非空元素,意味着它的子元素是完全可以像文本一样编辑。但是有时候,比如图像, 你可能想要确保 Slate 不会将元素的内容作为可编辑的文本,而是看做一个黑箱。

🤖 这个概念是从 HTML 借用的,请查看 空元素 (opens new window)

可以通过定义 editor.isVoid 函数来定义哪些元素被视为 void。(默认情况下,它总是返回 false。)

# 文本(Text)

文本节点是树中的最低级节点,包含文档的文本内容以及任何格式。它的接口是:

interface Text {
  text: string
  [key: string]: unknown
}
1
2
3
4

比如这个例子,加粗文本:

const text = {
  text: 'A string of bold text',
  bold: true,
}
1
2
3
4

文本节点可以包含任意的自定义属性,这就是你如何实现像是粗体斜体代码等自定义格式的办法。