Day 20. slate × Operation × Interface

https://ithelp.ithome.com.tw/upload/images/20211005/20139359LM3jXh5EDt.png

我们接下来就要进入到 slate 世界里的另一大领域: Operations 。

这一整段章节其实也是笔者准备整个系列文章中数一数二期待的章节。因为在面对 WYSIWYG 编辑器时,与 CRUD 相关的内容都一定会被放大检视。

想想我们目前看过的各种富文本编辑器五花八门的互动功能,随着时代演进各种行销手法的脑洞大开,各种花式 CRUD 的需求依序出现。开发者除了要正确实现功能逻辑外,效能方面的问题当然也不能马虎(拉个反白都会 lag 到不行的话谁还想用 XD)。

也因此每个 WYSIWYG 在开发过程中,底层实现 CRUD 相关功能的概念与逻辑必定会被慎重检视,尤其是像 slate 这种,将目标放在提供一个制作 WYSIWYG 编辑器的 framework library 更是如此。

为了提供开发者一个『扩展性强的底层核心』, slate 除了提供一系列平行於 DOM 的概念让开发者使用降低学习成本之外,它也在 CRUD 上额外下了许多功夫去拆分出不同的逻辑层以及处理掉常见的效能瓶颈,而这些正是我们在接下来的 Operation 与 Transforms 的章节所要探讨的重点!(讲一讲自己都有点兴奋了起来 ?

那麽整个篇章的小前言就到此为止,我们一样循序渐进,先回到 Interface 的章节被我们暂时略过的 operation.ts 这个 file 开始介绍起吧。


Concepts of Operation


https://ithelp.ithome.com.tw/upload/images/20211005/20139359SjUfFgFOKH.png

这个 file 与我们前面介绍 Interfaces 的章节里提到的 files 的 code 架构上大同小异,负责定义相对应概念的 types 以及提供它的 method apis ,它们之间的差别就在於: operation.ts 这个 file 里定义的 types 相对复杂许多。

... // Other types declaration

/**
 * `Operation` objects define the low-level instructions that Slate editors use
 * to apply changes to their internal state. Representing all changes as
 * operations is what allows Slate editors to easily implement history,
 * collaboration, and other features.
 */

export type Operation = NodeOperation | SelectionOperation | TextOperation

export interface OperationInterface {
	... // interface declaration
}

export const Operation: OperationInterface = {
	... // method apis implementation
}

从官方的注释( /*...*/ )里面我们可以先初步的了解到 Operation 负责掌控 slate editor 最基本最底层的状态改变,编辑器的一切修改最终都一定是透过一个或数个 operations 来达成,这让 slate 更好管理所有状态修改的纪录。

slate 将一切的状态操作都照着: Node 、 Selection 、 Text 这三大项来做分类。我们从 Operation 的 type 定义里就能看得出来。

export type Operation = NodeOperation | SelectionOperation | TextOperation

从 transforms 目录底下的分类也能看出这件事:

https://ithelp.ithome.com.tw/upload/images/20211005/20139359VNDPOXuSFA.png

从文字的描述上应该不难理解三者个别负责的部分:

  • Node 负责一切与节点( node )相关的操作
  • Selection 负责一切与『编辑器内的文字反白』相关的操作
  • Text 负责一切与纯文字相关的操作

那在我们开始 dig into 这三大项的 Operators 之前,我们要先渗透 low-level action 这件事所代表的含义。Operator 代表最基本的核心操作,就像 slate 不会去猜测开发者的使用情境,只提供最核心的功能一样,在里面定义的也都是最基本的 action 。

以我们第一个要介绍的 TextOperation 为例,这里头定义了一切 Text 所需的基本操作,但它却只由 extendable 的 InsertTextOperationRemoveTextOperation 所组成

export type InsertTextOperation = ExtendedType<
  'InsertTextOperation',
  BaseInsertTextOperation
>

export type RemoveTextOperation = ExtendedType<
  'RemoveTextOperation',
  BaseRemoveTextOperation
>

export type TextOperation = InsertTextOperation | RemoveTextOperation

以白话文来说明的话就是:一切与『文字』相关的操作都只需要由『插入』与『移除』文字这两种『事件』来完成。

再来是这三大类底下所有的 Operations 都是由一组 Base type 延伸并被定义为 extendable 的,开发者能依照自己的需求去扩展,所有的 Base type 里都同时存在一个 type property 负责描述 operation 的类别,剩下的就是各自所需的 properties 。

我们一样以TextOperation 底下的 Base type 为例:

export type BaseInsertTextOperation = {
  type: 'insert_text'
  path: Path
  offset: number
  text: string
}

export type BaseRemoveTextOperation = {
  type: 'remove_text'
  path: Path
  offset: number
  text: string
}

Properties inside :

  • type : operation type name
  • path : 欲修改的 node path
  • offset : text node start offset
  • text : 文字 value

接着到 SelectionOperation ,它只由一组 SetSelectionOperation 所组成

export type SetSelectionOperation = ExtendedType<
  'SetSelectionOperation',
  BaseSetSelectionOperation
>

export type SelectionOperation = SetSelectionOperation

以白话文来说明的话就是:一切与『反白』相关的操作都是由『设定反白』这个事件来完成,在 Base type 底下我们能看到他的参数组成单纯是由『旧资料』与『新资料』所组成:

export type BaseSetSelectionOperation =
  | {
      type: 'set_selection'
      properties: null
      newProperties: Range
    }
  | {
      type: 'set_selection'
      properties: Partial<Range>
      newProperties: Partial<Range>
    }
  | {
      type: 'set_selection'
      properties: Range
      newProperties: null
    }

Properties inside :

  • type : operation type name
  • properties : 旧的 selection properties ( Range type )
  • newProperties : 新的 selection properties ( Range type )

最後的 NodeOperation 由『插入』、『合并』、『移动』、『删除』、『设定』、『拆分』这几种事件所组成

export type InsertNodeOperation = ExtendedType<
  'InsertNodeOperation',
  BaseInsertNodeOperation
>

export type MergeNodeOperation = ExtendedType<
  'MergeNodeOperation',
  BaseMergeNodeOperation
>

export type MoveNodeOperation = ExtendedType<
  'MoveNodeOperation',
  BaseMoveNodeOperation
>

export type RemoveNodeOperation = ExtendedType<
  'RemoveNodeOperation',
  BaseRemoveNodeOperation
>

export type SetNodeOperation = ExtendedType<
  'SetNodeOperation',
  BaseSetNodeOperation
>

export type SplitNodeOperation = ExtendedType<
  'SplitNodeOperation',
  BaseSplitNodeOperation
>

export type NodeOperation =
  | InsertNodeOperation
  | MergeNodeOperation
  | MoveNodeOperation
  | RemoveNodeOperation
  | SetNodeOperation
  | SplitNodeOperation

基本的『插入』、『移动』、『删除』、『设定』这几种事件要传入的参数很直观,包含要做操作的路径( Path )、节点的资料内容、要移动到的新路径等等,就不繁琐地一一做说明了。

这边特别提一下『合并( MergeNode )』以及『拆分( SplitNode )』这两个事件:

export type BaseMergeNodeOperation = {
  type: 'merge_node'
  path: Path
  position: number
  properties: Partial<Node>
}

export type BaseSplitNodeOperation = {
  type: 'split_node'
  path: Path
  position: number
  properties: Partial<Node>
}

MergeNode 的做法是将提供的 path 指向的节点与它同层的前一个 sibling 做合并,而 position 代表着它前一个 sibling 的 childrentext character 的 index ,稍微偷看一下 transforms/node.ts 里的 mergeNodes method :

mergeNodes(
    // ...args
) {
	// ...

	let position

	// ...
    if (Text.isText(node) && Text.isText(prevNode)) {
		// ...
        position = prevNode.text.length
		// ...
    } else if (Element.isElement(node) && Element.isElement(prevNode)) {
		// ...
        position = prevNode.children.length
		// ...
    }
	// ...
}

它在这里的用途主要是提供编辑器的 selection 随着节点合并以後的更新依据。

SplitNode 则是将提供的 path 指向的节点子层,依照 position 之前与之後的 childrentext character ,拆分开来。

这边主要跟读者介绍这些 properties 的用途以及大概的运作模式,详细的实作我们留到之後介绍 Operation 的 transform method 时再介绍。


介绍完 Operations 所有的 Type 以後,下一篇我们要介绍一个完整的 Operation 所会经过的流程,也会大致介绍这些流程大致上负责的工作。

一样明天见罗~


<<:  Day 22:贪婪演算法(2)

>>:  [13th][Day20] http request header(下)

Day 0x14 UVa10035 Primary Arithmetic

Virtual Judge ZeroJudge 题意 输入两整数,求相加的过程需要进位几次 需要注...

Day 8:506. Relative Ranks

今日题目 题目连结:506. Relative Ranks 题目主题:Array, Sorting,...

[用 Python 解 LeetCode] (005) 189. Rotate Array

题干懒人包 给一个数组,旋转数组 K 次,K 非负数,如以下 附注:尽量想越多种解法越好,想到之後可...

DAY23 model展示

>model.py from logging import root from django....

【Vue】params vs. query | Vue Router

params - 命名的路由,加上参数让路由建立 url 动态的参数前要加上冒号 ":&q...