我們接下來就要進入到 slate 世界裡的另一大領域: Operations 。
這一整段章節其實也是筆者準備整個系列文章中數一數二期待的章節。因為在面對 WYSIWYG 編輯器時,與 CRUD 相關的內容都一定會被放大檢視。
想想我們目前看過的各種富文本編輯器五花八門的互動功能,隨著時代演進各種行銷手法的腦洞大開,各種花式 CRUD 的需求依序出現。開發者除了要正確實現功能邏輯外,效能方面的問題當然也不能馬虎(拉個反白都會 lag 到不行的話誰還想用 XD)。
也因此每個 WYSIWYG 在開發過程中,底層實現 CRUD 相關功能的概念與邏輯必定會被慎重檢視,尤其是像 slate 這種,將目標放在提供一個製作 WYSIWYG 編輯器的 framework library 更是如此。
為了提供開發者一個『擴展性強的底層核心』, slate 除了提供一系列平行於 DOM 的概念讓開發者使用降低學習成本之外,它也在 CRUD 上額外下了許多功夫去拆分出不同的邏輯層以及處理掉常見的效能瓶頸,而這些正是我們在接下來的 Operation 與 Transforms 的章節所要探討的重點!(講一講自己都有點興奮了起來 ?
那麼整個篇章的小前言就到此為止,我們一樣循序漸進,先回到 Interface 的章節被我們暫時略過的 operation.ts 這個 file 開始介紹起吧。
這個 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 目錄底下的分類也能看出這件事:
從文字的描述上應該不難理解三者個別負責的部分:
那在我們開始 dig into 這三大項的 Operators 之前,我們要先滲透 low-level action 這件事所代表的含義。Operator 代表最基本的核心操作,就像 slate 不會去猜測開發者的使用情境,只提供最核心的功能一樣,在裡面定義的也都是最基本的 action 。
以我們第一個要介紹的 TextOperation
為例,這裡頭定義了一切 Text 所需的基本操作,但它卻只由 extendable 的 InsertTextOperation
與 RemoveTextOperation
所組成
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 namepath
: 欲修改的 node pathoffset
: text node start offsettext
: 文字 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 nameproperties
: 舊的 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 的 children
或 text
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 之前與之後的 children
/ text
character ,拆分開來。
這邊主要跟讀者介紹這些 properties 的用途以及大概的運作模式,詳細的實作我們留到之後介紹 Operation 的 transform method 時再介紹。
介紹完 Operations 所有的 Type 以後,下一篇我們要介紹一個完整的 Operation 所會經過的流程,也會大致介紹這些流程大致上負責的工作。
一樣明天見囉~