简介
Slate.js
是一个完全可定制的的富文本编辑器,准确来说是一个框架。其诞生于 2016 年,作者是 Ian Storm Taylor
。它和 Draft.js
, Prosemirror
, Quill
类似,都是基于结构化对象来渲染富文本内容。
slate.js
架构设计类似于 MVC
:
(Model)
slate 定义了一套数据模型以及更新 model 的一系列 commonds。(View)
slate 定义了一套与数据模型对应的视图模型(洋葱模型),使用 react 将数据模型渲染成视图模型。(Ctrl)
slate 支持自定义事件监听,然后通过 commonds 调用更新数据模型。
这里,commonds
指的是 slate
内部定义的一系列 Operation
。和用来生产Operation
的一系列Transforms
辅助方法。model
更新以后会通过一些规则来保证数据格式的规范,对model
进行正确性校验,然后触发view
变更。
slate
仓库下包含四个 package
:
slate
:这一部分是编辑器的核心,定义了数据模型(model)
,操作模型的方法和编辑器实例本身。slate-react
:以插件的形式提供DOM
渲染和用户交互能力,包括光标、快捷键等等。slate-history
:以插件的形式提供undo/redo
能力。slate-hyperscript
:让用户能够使用JSX
语法来创建slate
的数据。
特点
- 灵活,完全可定制。
- 插件是一等公民,你可以以插件的形式定制自己的用于修改编辑器的行为API.
- 数据模型类似于可嵌套的
Dom
树,Schema
结构非常精简。 - 具有原子化操作
API
,支持协同编辑。 - 使用
React
作为渲染层;
slate 数据模型
slate
以树形结构来表示和存储文档内容,树的节点类型为 Node
,分为三种子类型:
export type Node = Editor | Element | Text
export interface Element {
children: Node[]
[key: string]: unknown
}
export interface Text {
text: string
[key: string]: unknown
}
Editor
是一种特殊的Element
,它既是编辑器实例类型,也是文档树的根节点Element
类型含有children
属性,可以作为其他Node
的父节点Text
类型是树的叶子结点,包含文字信息
用户可以自行拓展 Node
的属性,例如通过添加 type
字段标识Node
的类型(paragraph
, ordered list
, heading
等等),或者是文本的属性(italic
, bold
等等),来描述富文本中的文字和段落。
const initialValue = [
{
type: 'paragraph',
children: [{ text: '我是', bold: true }, { text: '一行', underline: true }, {text: '文字'}]
},
{
type: 'code',
children: [{ text: 'hello world' }]
},
{
type: 'image',
children: [],
url: 'xxx.png'
}
// 其他的继续扩展
]
slate 渲染模型
slate
数据模型通过slate-react
视图渲染以后的组件层级如下图所示:
你会看到一些 data-
开头的自定义内置特性(attribute
),比如 data-slate-node
等。
slate
主要内置特性如下:
Editable
data-slate-editor
用于标识编辑器组件。
Element
data-slate-node
: ‘element’|’value’|’text’;取值分别代表元素、文档全量值(适用于 Editable 上)、文本节点。data-slate-void
: 若为空元素则取值为 true,否则不存在。data-slate-inline
: 若为内联元素则取值为 true,否则不存在。
Leaf
data-slate-leaf
: 必须,取值为 true,表明对应 DOM 元素为 Leaf 节点。
String
data-slate-string
: 若为文本节点则取值为 true,否则不存在。data-slate-zero-width
: 若为零宽度文本节点则取值 ‘n’|’z’,分别指代换行、不换行,否则不存在。data-slate-length
: 用于标注零宽度文本节点的实际宽度,单位为字符数。默认为 0,如果不为零则为被设置了 isVoid 的元素的文本字符的宽度。
此外,对于 Element
的 attributes
中还有以下内置特性内容:
contentEditable
: 若不可编辑则取值为 false,否则不存在。dir
: 若编辑方向为从右到左则取值 ‘rtl’,否则不存在。ref
: 必选,当前元素的 ref 引用。Slate 会在每次 Element 渲染时将该元素和其对应 DOM 节点的映射关系添加到 ELEMENT_TO_NODE 的 WeakMap 中。若缺少 ref 则会因为 ELEMENT_TO_NODE 中映射关系的缺失而导致渲染失败和 toSlateNode 中报错。
简单实例
import React, { useState, useMemo } from 'react'
import { createEditor } from 'slate' // 创建编辑器实例的方法
import { Slate, Editable, withReact } from 'slate-react'
import { withHistory } from 'slate-history'; //以插件的形式提供 undo/redo 能力
// 初始化编辑器内容的数据。其结构类似于 vnode。
const initialValue = [
{
type: 'paragraph',
children: [ { text: '我是一行文字' } ]
}
]
export default BasicEditor = () => {
/** editor 变量为编辑器的对象实例,可以使用它提供的大量 API,也可以用来扩展其他插件。 */
const editor = useMemo(() => withHistory(withReact(createEditor())) ,[])
const [value, setValue] = useState(initialValue)
return (
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
<Slate
editor={editor}
onChange={newValue => setValue(newValue)}
>
<Editable/>
</Slate>
</div>
)
}
renderElement
slate.js 提供了 renderElement 让我们来自定义渲染逻辑,不过先别着急。富文本编辑器嘛,肯定不仅仅只有文字,还有很多数据类型,这些都是需要渲染的,所以都要依赖于这个 renderElement 。
renderLeaf
renderElement 是渲染 Element ,但是它管不了更底层的 Text ,所以 slate.js 提供了 renderLeaf ,用来控制文本格式。renderElement 和 renderLeaf 并不冲突,可以一起用。
自定义事件
在 slate.js,自定义事件可以直接在
操作API
到 Editor 和 Transforms 对象里封装了很多常用的一系列 API 。可以增加样式,操作节点等。
简单插件
slate.js 提供的是编辑器的基本能力,如果不能满足使用,它提供了插件机制供用户去自行扩展。
另外,有了规范的插件机制,还可以形成自己的社区,可以直接下载使用第三方插件。
插件开发其实很简单,就是对 editor 的扩展和装饰。你想要做什么,可以充分返回自己的想象力。
数据处理
其他概念
- contenteditable
- execCommand
- Selection 和 Range
- 自定义组件