上一篇我们有提到上图这些画了黄框的 files ,是我们在建立 editor 与操作 editor value 时主要会使用到的概念。
同时介绍了 slate document model 里需要用到的 concepts : text 、 element。
紧接着今天我们就要把范围扩大到整个 Data-Model 的层级了,也就是要纳入 editor 与 node 这两个概念。
一样一个一个来,我们先从 editor.ts 开始,里面主要定义了三种 type 分别是: Selection
、 Editor
、 NodeMatch
Selection
export type BaseSelection = Range | null
export type Selection = ExtendedType<'Selection', BaseSelection>
如其名,主要是拿来纪录编辑器里使用者当前的反白区块,对 DOM 有一定熟悉程度的读者就会知道,slate 里的 Selection
与 Range
这两个概念完全就是来自於 Web api 所提供的 Selection
、 Range
这两个 object ,连 slate 的 Range
里头的 anchor
、 focus
也完全是在模仿浏览器 Selection
里的 anchorNode
、 focusNode
properties ,这边提供个 MDN 连结给读者,有兴趣可以上前查看,看完这段觉得一头雾水的读者也不用担心,关於 slate 的 Range 我们在 下一篇 就会详细介绍到它了,这边先知道 Selection 是拿来纪录 user 反白的区域即可。
NodeMatch
/**
* A helper type for narrowing matched nodes with a predicate.
*/
export type NodeMatch<T extends Node> =
| ((node: Node, path: Path) => node is T)
| ((node: Node, path: Path) => boolean)
其实作者留给它的注释就写地蛮清楚的了,它是用来让开发者比对 Node 的 function type ,它在 library 中只出现在具有迭代性质的 method apis 里的 match
option ,slate 会透过开发者传入的 match
function 来判断当前迭代到的 Node 是否要跳过计算,藉此来提升运算的速度。
Editor
/**
* The `Editor` interface stores all the state of a Slate editor. It is extended
* by plugins that wish to add their own helpers and implement new behaviors.
*/
export interface BaseEditor {
children: Descendant[]
selection: Selection
operations: Operation[]
marks: Omit<Text, 'text'> | null
// Schema-specific node behaviors.
isInline: (element: Element) => boolean
isVoid: (element: Element) => boolean
normalizeNode: (entry: NodeEntry) => void
onChange: () => void
// Overrideable core actions.
addMark: (key: string, value: any) => void
apply: (operation: Operation) => void
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteFragment: (direction?: 'forward' | 'backward') => void
getFragment: () => Descendant[]
insertBreak: () => void
insertFragment: (fragment: Node[]) => void
insertNode: (node: Node) => void
insertText: (text: string) => void
removeMark: (key: string) => void
}
export type Editor = ExtendedType<'Editor', BaseEditor>
代表的就是编辑器本身,主要的 Data model 有四大项:
children
纪录 document-model valueselection
纪录编辑器当前反白区域operations
纪录一组 FLUSHING 内触发过的 operations listmarks
提供了一个暂存空间,储存文字节点内除了 text
属性之外的自定义属性资料,会在下次插入文字时赋予它们这些属性资料。除此之外它也同时提供 overridable 的 behaviors :
onChange
在 Day10 介绍过了,负责通知 view layer 的 render
isInline
与 isVoid
又是两个从 DOM 的 inline element 以及 void element 借用过来的概念了,来看一下 create-editor.ts 里面是如何实现这两个函式的
isInline: () => false,
isVoid: () => false,
蛤?这也太短了吧!
是的没错就是这麽短!这两个函式的功用是预设让开发者去撰写判断传入的 element 是否为 inline-element 或 void-element 的函式,而这两种属性的预设值皆为 false
,开发者可以自行依照自己的需求去更改它的定义。
const { isInline } = editor
editor.isInline = element => {
return element.type === 'link' ? true : isInline(element)
}
normalizeNode
这个 method 的 code 实作细节我们会在後面 Operation 的篇章再做介绍,目前先来看看官方文件上 normalizeNode
的 override 范例:
const { normalizeNode } = editor
editor.normalizeNode = entry => {
const [node, path] = entry
if (Element.isElement(node) && node.type === 'link') {
// ...
return
}
normalizeNode(entry)
}
其实 slate 提供的 override example 都像上图的范例一样,把原本定义好的函式当作 callback function 使用。
接着就要来收回我在 Day8 时挖给自己的坑了 XD
// Overrideable core actions.
addMark: (key: string, value: any) => void
apply: (operation: Operation) => void
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteFragment: (direction?: 'forward' | 'backward') => void
getFragment: () => Descendant[]
insertBreak: () => void
insertFragment: (fragment: Node[]) => void
insertNode: (node: Node) => void
insertText: (text: string) => void
removeMark: (key: string) => void
剩余的这些 core actions 除了 apply
以外,其他的实质上都是我们当时提到的新版 slate 的 Commands ,我们来看一下 editor.ts 里的 method apis 里面的 code 就会了解了
export const Editor: EditorInterface = {
// other methods...
addMark(editor: Editor, key: string, value: any): void {
editor.addMark(key, value)
},
deleteBackward(
editor: Editor,
options: {
unit?: 'character' | 'word' | 'line' | 'block'
} = {}
): void {
const { unit = 'character' } = options
editor.deleteBackward(unit)
},
deleteForward(
editor: Editor,
options: {
unit?: 'character' | 'word' | 'line' | 'block'
} = {}
): void {
const { unit = 'character' } = options
editor.deleteForward(unit)
},
// ...and so on
}
editor 里的 method apis 的数量是远超过上面所列举的内容的,不过只要是负责操纵 editor value 的 methods ,都只是去 call editor 里同名的 core actions ,这边只是帮你多包一层依赖注入而已。
而这就是新版 slate 的 "Command" 的真面目,开发者想以 plugin 的方式自定义或 override editor 的 core actions 也可以,自定义 method api 或 override 里头的 methods 也随便你,它就是提供给你两个 object 随你操纵,重点依旧是你如何操作 Transforms 与 Operations 。
那 apply
呢?
它是我们主要操纵 Operations 的地方,这部分我们等到 Transforms 与 Operations 的章节会再详细介绍。
经过了 text 、 element 、 editor 这一连串的概念与范例轰炸以後,相信大家越来越能感受到 slate 追求与 DOM 相似这件事所谓何事了,而最後的这个 node.ts 可以说是整个 slate 里最重要的一个 concept ,更是他们追求相似於 DOM 这件事的集大成。
slate editor 将顶层的 Editor
、 中间的 Element
容器、底层的 Text
这三种 interfaces 视为个别的 Nodes
,并由他们来组成一整组的 slate node tree ,同时它的巢状架构让一个节点可以拥有无限个子节点,如果我们把一组 editor 里的其余 properties 拔掉,只留 document model 的话就会长得如下
const editor = {
children: [
{
type: 'paragraph',
children: [
{
text: 'A line of text!',
},
],
},
],
// ...the editor has other properties too.
}
Slate Document 也没有限制只能存在一组 Editor node ,因此我们也可以用下图来诠释 node concept 在 Slate Document 之下的全貌。
是不是看起来就跟 HTML DOM tree 一模一样呢?
接着就直接来看看 node.ts 里面定义了哪些 types
/**
* The `Node` union type represents all of the different types of nodes that
* occur in a Slate document tree.
*/
export type Node = Editor | Element | Text
/**
* The `Descendant` union type represents nodes that are descendants in the
* tree. It is returned as a convenience in certain cases to narrow a value
* further than the more generic `Node` union.
*/
export type Descendant = Element | Text
/**
* The `Ancestor` union type represents nodes that are ancestors in the tree.
* It is returned as a convenience in certain cases to narrow a value further
* than the more generic `Node` union.
*/
export type Ancestor = Editor | Element
Node
type 就如我们先前介绍过的一样,是 Editor
、 Element
、 Text
的 union ,Descendant 与 Ancestor 则各自为『可以成为子层节点』与『可以成为父层节点』的 types union ,它们是为了定义父/子层这两种概念的集合而存在的,回头看一下 前一篇 介绍的 Element
type
export interface BaseElement {
children: Descendant[]
}
这种定义方式的用意就是在描述『 children
property 相对应的 type 是子层的 type 集合』。
Node
、 Descendant
、 Ancestor
这三种 types 会经常在我们开发 slate 时被使用到,尤其是 Descendant ,因为太常出现需要针对『子层集合』这个概念进行操作的情境了,撇除掉开发者自行开发,就连在 method apis 里面也经常会看到他们的身影。
在 node.ts 里还有额外定义的 NodeEntry
、 NodeProps
这两种 types
/**
* `NodeEntry` objects are returned when iterating over the nodes in a Slate
* document tree. They consist of the node and its `Path` relative to the root
* node in the document.
*/
export type NodeEntry<T extends Node = Node> = [T, Path]
/**
* Convenience type for returning the props of a node.
*/
export type NodeProps =
| Omit<Editor, 'children'>
| Omit<Element, 'children'>
| Omit<Text, 'text'>
NodeEntry
跟我们前一篇提到过的 ElementEntry
type 的性质上很相近,大多都是用在 iterate 的功用上,详细内容我们一样放到之後再讨论
NodeProps
搭配 node.ts method apis 里的 extractProps
method 让我们取得传入的 node 其余自定义的 properties
/**
* Extract props from a Node.
*/
extractProps(node: Node): NodeProps {
if (Element.isAncestor(node)) {
const { children, ...properties } = node
return properties
} else {
const { text, ...properties } = node
return properties
}
},
让我再试着来统整一次今天的内容吧!
我们先从 editor.ts 的内容开始,提到了selection
的源头是来自於 Web api 的 Selection 概念。介绍了NodeMatch
的用途与常用在 method apis 里的match
options 分别介绍了editor
里的各个 properties 与 methods ,再回头补足了 core actions 与 Command 之间的关系。
最後介绍了集大成 node.ts 的Node
union type 以及NodeProps
。
然後 ... 就没有然後了,就两个 files 的内容而言要吸收的概念也不少呢!
毕竟 Data-Model 也算是 slate 的重点之一呢,要想开始使用 slate 做开发,这篇的内容绝对算是最基本的入门门槛。
紧接着下一篇我们要来介绍 /Interface 里最後的重点,也就是 slate 是如何去做文字定位( Position )的,这又会是另一场硬仗了,一样明天再见真章吧!
<<: day12 轻松一下,用 coroutine 接个 restful api
>>: [Day27] 超萌❤ 教你用Python画天竺鼠车车逗女友开心!
前言 Google 翻译团队在2016年发表了重要文章《Google’s Neural Machin...
如图 pancode: div 设计成 各种形状 三角形。五角形 六角形 的方法 制作参考引用 ht...
第六章函数与递回,强调的是函式原型(function prototype)又称为函式宣告(func...
感谢大家花宝贵的时间阅读这系列的文章,由於篇幅有限,其实还有很多主题无法尽录,不过希望阅读过後,大...
https://leetcode.com/problems/two-sum/description...