iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Modern Web

深入 slate.js x 一起打造專屬的富文字編輯器吧!系列 第 10

Day 10. slate × 架構藍圖

https://ithelp.ithome.com.tw/upload/images/20210925/20139359VX8Zaa1bH9.png

在開始今天的主題前,雖然在前面介紹 Slate 時已經有稍微提到過了,我們還是先從 slate 的各個 packages 分別負責的領域開始介紹起。

https://ithelp.ithome.com.tw/upload/images/20210925/20139359QCjGFcjiJs.png

  • slate:Slate 的資料層 package ,負責一切與核心模型相關的邏輯處理,也是本篇章的主角。
  • slate-history:提供 Slate 支援 undo/redo 功能的 package 。
  • slate-hyperscript:協助 Slate 進行 JSX Deserializing 功能的 package ,感興趣的讀者可以到官方文件的 Serializing 頁面一探究竟
  • slate-react:官方提供的 react view layer 的 slate implemetation 。

slate Directory


首先第一步先從確定工作量開始,我們直接來看 slate package 的 src 之下的 directory 吧!

https://ithelp.ithome.com.tw/upload/images/20210925/20139359MeslayaotS.png

咦?這個量怎麼跟我預期的不太一樣,它不是負責處理整個核心邏輯的嗎?應該要多一點才對吧?


這就是 Schema-less 這項特性的美妙之處了,我們在 Day8 的時候也有提到過 Slate 本質上是提供給開發者一個 full-customizable 的 DOM like editor , 它不會猜測各種複雜的使用情境,只提供給開發者最基本的 concepts 以及 method-apis,也因此它的工作量比一般的 libraries 要少上許多。

我們先為上圖的第一層 folders 與 file 區分他們所負責的功能:

  • interfaces/ :定義了所有 slate 使用到的概念的 Base type(同時也包含 BaseEditor 的 interface 以及 low-level action 的 Operation types),以及這些概念所提供的 method apis 。

  • transforms/ : Transforms 、 Operations 為在新版 Slate 的世界中,二個主要操作 immutable editor 的方法,讓開發者可以透過它們來自由操作 editor state 。

    其實 Slate 裡還有一個詞叫 "Command" ,在新、舊版的 Slate 中都有出現,卻代表不同的意義,筆者一開始也被這幾個詞搞得暈頭轉向。

    舊版 Slate 的 Command 其實就是新版的 Transforms ,他們的用途一樣是提供給開發者操作 editor 的 methods ,只是新版另外整理成一套更完整的介面給開發者使用,他們本質上是一樣的。

    新版 Command 的重點則是 "Custom" 這一詞,是提供給開發者搭配 Transforms 與 Operations 去製作貼近於自己創造的編輯器 use-case 的 reusable function ,官方文件上是將它視為 high-level actions ,但其實看起來就是提供一個名義讓開發者自由搭配 Transforms 與 Operations 去建立自己的 helper function ,可能留個名字給它看起來比較炫砲吧 XD

    附上 官方文件 上提供的寫法,讀者可以先加減看一下,我們在之後介紹到 interface/editor.ts 的文章時會再提到它。

    import { Editor } from 'slate';
    
    const MyEditor = {
      ...Editor,
    
      insertParagraph(editor) {
        // ...
      },
    }
    
  • utils/ :一些輔助 slate package 內部使用的函數

  • create-editor.ts :提供負責 build 並 return immutable editor 的 create function ,而 function 裡做的事就是初始化 editor 裡頭的資料,包含 data model ,以及各種 behaviors 與 actions 。

    來偷瞄一眼 interfaces/editor.ts 裡 BaseEditor 的 interface 是怎麼定義的

    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
    }
    

    這就是 slate 回傳給開發者的 editor state 的 interface 。

    childrenselectionmarksoperations 儲存了整個 slate editor 需要的資料,我們會在 Data-Model 的篇章 深入介紹。

    再來有個值得注意的是 onChange 這個 function , 在執行 Transform methods 時, Slate 其實會在內部觸發多次的 Operations 分次去修改 Editor value ,如果我們只純粹以 State change 作為畫面 re-render 的判斷依據的話就會導致『一次 Transform 就伴隨著多次不必要的 re-render』,這當然不是我們所樂見的, Slate 也知道我們不喜歡,於是它利用了 Promise Micro-Task 製作了 FLUSHING 機制,提供 onChange 這個 overrideable 的 function 讓開發者能在正確的時機點 re-render 畫面。

    其餘的則為官方提供的 build-in methods ,開發者可以搭配使用這些 Overrideable actions 去建立 Command


slate Process


接著我們上個簡單的 whimsical 圖來看看 Slate 的基本運作概念:

https://ithelp.ithome.com.tw/upload/images/20210925/20139359tct3vu7wnD.png

  1. 我們首先從 createEditor 函式取得 editor ,並主要透過 Transform methods 操作 editor state。
  2. Transform 的底層會再經過一系列的 Operations 更新 Editor values ,其中 childrenselection 會再經過一層 Immer 的包裝做轉換。
  3. 等到一系列的 Operation 完成, Slate 標示 " Finish Flushing " 後再觸發 onChange function 。

如果我們試著省略中間較為繁瑣的過程,將它們概括為一個週期產生一組的 Actions (行為)的話,不難發現它其實就是一組經典的單向資料流的模型。

https://ithelp.ithome.com.tw/upload/images/20210925/20139359DLJ4ckRten.png

這就是最基本、簡化過後的 slate 運作模型,先讓讀者有個畫面,接下來我們會將目光聚焦於 interfaces/ ,那我們就一樣下一篇見啦!


上一篇
Day 9. Compare × Final
下一篇
Day 11. slate × Interfaces × Document-Model
系列文
深入 slate.js x 一起打造專屬的富文字編輯器吧!30

尚未有邦友留言

立即登入留言