iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Modern Web

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

Day 7. Compare × G2 × Draft

https://ithelp.ithome.com.tw/upload/images/20210922/20139359RnNSRcD00E.png

接下來的 Draft 與 Slate 就是提供建立編輯器環境為主的 Framework Library 了,如果對這個名詞不太熟悉的話可以回頭去看 Day5 底下的介紹。

https://ithelp.ithome.com.tw/upload/images/20210922/20139359YqAHT6IFeU.jpg

一樣附上 Draft.js 的 document link

Draft.js 是筆者認為使用表現上與 Slate.js 最相似的 Library了(其實按照誕生的順序與目前的下載次數來比較的話應該是 Slate 向 Draft 看齊才對XD),在網路社群上也能時不時看到這兩個 libraries 相互做比較的文章。

相較於前面文章的 Libraries 是直接請你把他們提供給你的 editor 綁在特定的 DOM 上,主要都是在 init 階段調整 editor config 改變呈現的樣式與功能,頂多像 Quill 這樣提供額外的客製化規則,但到頭來都還是使用套件提供給開發者的,現成的、大致樣式都已經決定好的編輯器。

Draft 與 Slate 則是提供開發者一個 immutablesingleton editor state ,他們提供了各種『開發一個編輯器會需要的操作 api』,以及在 create 之後回傳一組 Editor state machine 。

『它們做的更像是提供開發者一組畫紙與畫筆,開發者想呈現什麼樣的內容全由自己決定。』

這也是為何它們會以 Editor framework 自居。

我們上個起手式範例圖讓大家有個畫面。

首先是 Draft:

import React, {useState} from 'react';
import {Editor, EditorState} from 'draft-js';

const DraftEditorExample = () => {
	const [editorState, setEditorState] = useState(EditorState.createEmpty());

	console.log('editorState-->\n', editorState);

	return (
		<Editor
			editorState={editorState}
			onChange={setEditorState}
		/>
  );
}

https://ithelp.ithome.com.tw/upload/images/20210922/20139359Zz5NsTvaXz.png

再來上 Slate:

import React, { useState } from 'react';
import { createEditor, Descendant } from 'slate';
import { Slate, Editable, withReact } from 'slate-react';

const initialValue: Descendant[] = [
  {
    type: 'paragraph',
    children: [
      { text: 'This is editable plain text, just like a <textarea>!' },
    ],
  },
];

const SlateEditorExample = () => {
	const [value, setValue] = useState(initialValue);
	const [editor] = useState(withReact(createEditor());

	console.log('editor-->\n', editor);
  return (
    <Slate editor={editor} value={value} onChange={value => setValue(value)}>
      <Editable placeholder="Enter some plain text..." />
    </Slate>
  );
};

https://ithelp.ithome.com.tw/upload/images/20210922/20139359zZwe64WQtl.png


記得在第二篇時你有提到,Draft 是專門為 React 所設計的。Quill 或其他的 Libraries 也蠻明顯是針對 Plain JS 做開發的,那 Slate 呢?


關於這部分我們在之後深入解析 Slate source code 的章節時會再詳細介紹。

這邊先說結論,Draft 就如同官方所宣稱的一樣,是一個 build for React 的 Editor framework library,而 Slate 官方提供的 packages 也是針對在 React 的環境下做開發的,這點跟 Draft 一樣,可以說專案如果是建立在 React 之上又需要做到大量的客製功能的話就直接從兩者之間擇一也不為過。

然而不一樣的是 Slate 因為在專案設計上的差異,他也能讓開發者在不同的框架下使用它,事實上無論是 Vue 或 Angular 等不同的框架,我們都能找到其他團隊建立好的,Implement Slate 的 view-layer libraries。

可惜的是新版本的 Slate 目前仍在 beta 版本,因此除了官方提供的套件之外,其他的都比較不受關注,穩定程度就相對低了些,所以如果是在別的框架底下開發 Slate 的話一般還是會建議自己建立一份依賴於 Slate 上的 view-layer 。

最關鍵的問題果然還是他們之間的比較了,前面你也有提到這兩個 Library 時常被拿來做比較,在開發上有什麼依據能協助我們在他們之間做選擇的嗎?


首先是 Draft 整體專案的活躍度與穩定度是高於 Slate 不少的,雖然它本身提供的 editor state 與針對 state machine 的 api 操作是不開放讓開發者自行擴充的,不像 Slate 主打『視擴充套件為第一公民』,提供的 api 與其他操作都能很大幅度的任由開發者擴充。

我們依然能在 Draft 的社群上看到主流的擴充套件: draft-js-plugins 。協助開發者可以更有系統性地去管理各種不同的功能以及渲染的樣式。

這個擴充套件讓開發者可以在 Editor 上丟入一個 plugins props , plugins 裡存放了各種自定義功能的樣式與互動邏輯:

const plugins = [linkifyPlugin, hashtagPlugin];

return (
  <Editor
    editorState={this.state.editorState}
    onChange={this.onChange}
    plugins={plugins}
  />
);

套件本身已經有提供一些 built-in 的功能讓開發者可以直接使用的,但開發者當然也能自己實作出其他的功能

const Link = ({entityKey, children}) => {
	// ...
  return (
    // ... Render component
  )
}

const linkifyPlugin = () => {
  return {
    decorators: [
			// ... decorators implementation
		],
    handlePastedText: (text, html, {getEditorState, setEditorState}) => {
	    // ... handlePastedText implementation
    },
  }
}

export default linkifyPlugin

如果是要應付一般常見的功能的話 draft 也很夠用了,但他仍然有一些限制是需要注意的,以下就是 Draft 在社群上時常被挑出來的問題點:

Flat Document model


與 Quill.js 一樣, Draft 的 Document model 也是由一個名為 ContentState 的 class 所定義的 Implementation 。

Draft 提供了 convertToRawconvertFromRaw 這兩種 methods 。前者用於把 Immutable 的 ContentState instance 轉換為 plain JSON object ;後者用於把 plain JSON object 轉回 ContentState instance 。

我們來看一下透過 convertToRaw method 轉換出來的 plain JSON object 範例:

https://ithelp.ithome.com.tw/upload/images/20210922/20139359wYu3JOEKZZ.png

https://ithelp.ithome.com.tw/upload/images/20210922/20139359ezyUOhA4oe.png

圖片出處

我們可以看到各種針對單一 block 屬性的資料定義,例如: type 紀錄 block 的類別、text 儲存文字内容、 inlineStyleRanges 紀錄了內文的樣式,等等。

但這樣的資料結構卻是扁平狀的,意思就是我們無法在一個 block 底下存入另一個 child block ,無法將整個 Document model 設計成樹狀結構。

這就導致了當開發者有建立巢狀結構相關功能的需求,例如:table 、 inline-quote 時會變得格外的複雜 ,甚至有很大的機會需要製作巢狀的 Draft editor ,讓管理整個編輯器的複雜程度向上跳躍了一大層級。

Afterthoughts of Serialize & De-serialize


Draft 並沒有內建對 HTML 的 serialization 與 de-serialization ,它內建提供給開發者的功能就只有在 ContentState 與 Plain JSON object 之間的轉換而已。

這也是開發 Draft 時會特別需要頭痛的問題,因為這會直接影響到『 copy & paste 』這項功能是如何實現的,如果遇到底層是以 G1 編輯器作為核心來實作的編輯器的話,因為傳過來的資料是 HTML 格式的,因此勢必會遇到需要將 HTML 轉換為 ContentState 格式( HTML2Draft )的情境。

同時也因為 ContentState 的格式架構與原生的 DOM 架構存在不小的差異,提高了開發者在實現相關功能時所需花費的成本,當然使用現成的套件去輔助完成這項功能也是一個選擇,但穩定度與功能實作地好不好就又是另一個需要花時間討論的議題了 ...


今天對於 Draft 的討論就到這邊,同時針對其他 Libraries 的介紹也到此為止,緊接著就輪到介紹我們的主角 Slate 的特色了,一樣讓我們下篇文章見囉~!


上一篇
Day 6. Compare × G2 × Quill
下一篇
Day 8. Compare × G2 × Slate
系列文
深入 slate.js x 一起打造專屬的富文字編輯器吧!30

尚未有邦友留言

立即登入留言