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 8] Reactive Programming - Reactor(FLUX & MONO) Part 2

>>:  Day07-Kubernetes 那些事 -Label 篇

DAY29-JAVA的for-each、Iterator和ListIterator

集合的特性 可以依照集合是否具有「自动排序性」、「重复性」、「次序性」及「使用关键值」,为资料选择适...

第三章

接续前一章节提到的空间,最後还是选了便宜的Hostinger,他有着无比底廉的价格,并且在测试使用时...

建立 Google App Script 专案(2)

今天目标是成功筛选出特定条件的信件内容,方便我们之後 push 到资料库供 Line Messagi...

Day29-保护鲸鱼人人有责(四)

前言 前几天不管是讲怎麽把 Dockerfile 写好、还是做弱点扫描,基本上都是在确保 Docke...

[DAY 30]铁人赛完赛结语

今天是铁人赛最後一天 我把之前所整理的资料来源一并附上 有些功能其实已经想好大概怎麽实现了,但在30...