iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
Modern Web

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

Day 16. slate × Interfaces × CustomType

https://ithelp.ithome.com.tw/upload/images/20211001/20139359JwqC2Afvj6.png

slate 將 typescript 的型別擴充相關的內容都集合在 interfaces/custom-types.ts 這個 file 裡面。

這個 file 的 code 是由一名叫 thesunny 的 contributor 針對新版 slate 處理 custom types 的修改建議。

因為在之前的版本,開發者需要在每次使用 api call 時都得重複以 generic 的形式傳 custom-types ,這份 file 出現的目的正是讓開發者在開發 slate project 時只需定義一次 custom-types 就能一勞永逸。

Slate Custom-Types


這裡頭主要功能的部分只有短短地不到 10 行,卻利用了 interface 的 declaration merging 、 type 的 discriminating union 以及 unknown 的特性一次包辦了整個 type customizing 所需的一切功能,筆者當初看到的時候覺得真的是太厲害了!特地拉成一整篇文章與各位讀者分享。

我們先直接來看一下 custom-types.ts file 裡面的 code

/**
 * Extendable Custom Types Interface
 */

type ExtendableTypes =
  | 'Editor'
  | 'Element'
  | 'Text'
  | 'Selection'
  | 'Range'
  | 'Point'
  | 'InsertNodeOperation'
  | 'InsertTextOperation'
  | 'MergeNodeOperation'
  | 'MoveNodeOperation'
  | 'RemoveNodeOperation'
  | 'RemoveTextOperation'
  | 'SetNodeOperation'
  | 'SetSelectionOperation'
  | 'SplitNodeOperation'

export interface CustomTypes {
  [key: string]: unknown
}

export type ExtendedType<
  K extends ExtendableTypes,
  B
> = unknown extends CustomTypes[K] ? B : CustomTypes[K]

ExtendableTypes 不難理解,它就是事先為可以進行擴充的 types 做好限制,在 ExtendedType 吃的第一個 generic type 有限制了它的範圍必須縮限在 ExtendableTypes 之中,這也是為什麼我們在 Day11 的 Example type 範例放進 ExtendedType 裡是不會過關的原因。

CustomTypes 是主要讓開發者定義 custom types 的 interface ,利用 interface 可以 extend 的特性讓開發者可以透過 declare module 等方式擴充。

拿 slate 提供的 example code 作為範例,引入下方的 file 後 CustomTypes 就會多了 EditorElementText 這三組 keys 的定義:

declare module 'slate' {
  interface CustomTypes {
    Editor: CustomEditor
    Element: CustomElement
    Text: CustomText | EmptyText
  }
}

最後是最精華的 ExtendedType utility ,它吃了兩個 type generics 分別是:

  • K : 縮限於上方的 ExtendableTypes ,負責比對 CustomTypes 中指定的 key 類型是否被擴充定義。
  • B : 傳入的 base-type ,如果對應到的 key 沒有在 CustomTypes 中被擴充定義的話會原封不動地回傳 B 。

讓我們搭配最基本的 Text type 協助我們介紹

/** text.ts */
export interface BaseText {
  text: string
}

export type Text = ExtendedType<'Text', BaseText>

/** custom-types.ts */
export type ExtendedType<
  K extends ExtendableTypes,
  B
> = unknown extends CustomTypes[K] ? B : CustomTypes[K]
  1. 在我們將 'Text'BaseText 傳入 ExtendedType 以後,它首先會確認 'Text' 是否存在於 ExtendableTypes 裡。
  2. 接著進行 unknown extends CustomTypes[K] 的三元判斷式,這裏運用了 unknown 只會 extends unknown 這項特性,當判斷結果為 true 時代表開發者未在 CustomTypes 裡對 K 做擴充,回傳 base-type 給 Text,反之則回傳 CustomTypesK 對應到的擴充內容給 Text

最後再附上 Slate Github issue 的討論串,筆者在深入了解以前都只是照著 slate 提供的 example code 依樣畫葫蘆而已,一直將納悶放在心中。希望今天的分享也能讓讀者們體會筆者當時備受震懾的心情 XD

到這篇為止我們的 interface/ 章節終於要畫上句點了,接下來我們要先花些篇幅來聊聊 Immutable 這項議題,以及它、 Immer.js 、 Slate 之間的關係。

咱們明天見囉~


上一篇
Day 15. slate × Interfaces × Iteration
下一篇
Day 17. slate × Immutable
系列文
深入 slate.js x 一起打造專屬的富文字編輯器吧!30

尚未有邦友留言

立即登入留言