十分鐘搞定 React Draft Wysiwyg文字編輯器

最近正在進行的專案遇到了文字編輯器的需求,發現市面上已經有許多強大的文字編輯器,既然已經有大神做的現成品,我當然就不需要重複造輪子啦(其實根本造不出來)。爬了一下文就決定使用 React Draft Wysiwyg,它的造型簡潔乾淨、功能齊全,看起來又很容易應用,完全符合我的需求。

官網上的文件很詳細、清楚,我們就照著說明一步一步來做吧。

首先用yarn或npm安裝

npm install -s draft-js

npm install -S react-draft-wysiwyg

yarn add ‘draft-js’

yarn add ‘react-draft-wysiwyg’

安裝好之後就來import

import React, { Component } from ‘react’;

公司的專案是使用Next.js來進行伺服器端渲染(Server Side Render),因此我的樣式是import在pages下面的app.js,直接讓它變成全域的樣式(不良示範),改天再來分享Next.js在使用上容易踩的坑。

import完之後馬上就可以使用啦!<Editor/>外的邊框是我自己加的,可加可不加。

export default class TextEditor extends Component{
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};

this.onEditorStateChange = this.onEditorStateChange.bind(this);
}

onEditorStateChange(editorState) {
this.setState({editorState,});
};

以上寫完之後馬上就成功得到一個所見及所得的文字編輯器啦,來看看它長甚麼樣子:

看看那個精美的工具列,是不是覺得有些功能根、本、用、不、到!React Draft Wysiwyg提供了我們toolbar這個property來管理工具列,官方文件上有完整的toolbar,我們就留下自己想要的功能就好:

<Editor
toolbar={{
options: [‘inline’, ‘blockType’, ‘fontSize’, ‘textAlign’,
‘history’, ‘colorPicker’],
inline: {
options: [“italic”],
bold: { className: “demo-option-custom” },
italic: { className: “demo-option-custom” },
underline: { className: “demo-option-custom” },
strikethrough: {className: “demo-option-custom” },
monospace: { className: “demo-option-custom” },
superscript: {className: “demo-option-custom”},
subscript: { className: “demo-option-custom” }
},
blockType: {className: “demo-option-custom-wide”,
dropdownClassName: “demo-dropdown-custom”},
fontSize: { className: “demo-option-custom-medium” }
}}
initialEditorState={editorState}
wrapperClassName=”demo-wrapper”
editorClassName=”demo-editor”
onEditorStateChange={this.onEditorStateChange}
/>

options和inline裡的元素從字面上很好理解,甚麼功能不需要拿掉就好。改好程式碼,馬上來試用我們的文字編輯器,是不是10分鐘就搞定了!

接下來如果要把使用者輸入的內容存到後端該怎麼辦呢?首先輸入的值一定在editorState裡面,我們就來console.log(this.state.editorState),看看它長什麼樣子。

現在editorState是個物件,裡面包括了很多function,就是那些讓我們可以編輯文字樣式的功能,這很明顯不是我們要存到後端的樣子。那應該怎麼存這個值呢?讓我們先來了解這個編輯器的運作原理,我們對輸入的值進行任何編輯的動作,編輯器就會將相對應的Html tag和css樣式append上去,如果要保存並得到編輯的結果,第一步就是要從這個物件中取得這些Html tag和我們輸入的值。

需要用的是編輯器內建的function:getCurrentContent()和draft-js提供的function:convertToRaw。getCurrentContent()可以直接使用,convertToRaw需要import。

import { EditorState, convertToRaw } from ‘draft-js’;

為了方便操作我新增了一個state,取名為message,用來存取我要的值。

然後我們還需要用到draftjs-to-html這個套件,馬上來安裝。

npm install -s draftjs-to-html

安裝好之後記得import。

import draftToHtml from “draftjs-to-html”;

接下來在onEditorStateChange這個function裡就可以工作了,先用getCurrentContent()取得editorState這個物件裡我們要的值,再把這個值帶進convertToRaw這個function裡,最後在setState時把剛剛那一串操作塞到message裡,直接來看程式碼:

順便來console.log一下message看看我們做到甚麼階段了,然後這邊順便推薦一下setState的callback function,非常方便,可以善加利用。

現在message的值是一個字串(string):”<p>hello world</p>”,給自己一個掌聲鼓勵,到這個階段我們已經可以把輸入的內容變成字串形式的Html存到後端了,接下來要處理的是,之後我們從後端撈出這個message要怎麼把它變成這個編輯器可以編輯的形式(也就是一開始的那個物件editorState)呢?

這裡我們需要的是html-to-draftjs這個套件,一樣先安裝再import。

npm install -s html-to-draftjs

接著我們新增一個function來處理這件事,這裡稍微有點複雜,先來看程式碼:

來解釋一下這個function做了甚麼事情,首先帶進我們從後端撈出的message,把message帶進htmlToDraft這個function裡,然後assign給變數contentBlock(我很懶所以直接用文件上的變數名稱),在contentBlock存在的情況下,從htmlToDraft的prototype找到contentBlocks這個property,然後再帶進內建的function:ContentState.createFromBlockArray裡,把這個值assign給變數contentState(一樣不是我取的),最後在setState時把contentState帶進內建的function:EditorState.createWithContent裡。如此一來message就變成編輯器可以操作的物件並存在editorState裡了。

呼~,做完這些我們離成功還剩下一步,當我們使用這個編輯器生成一段內容,存到後端,再從後端撈出,它現在是一段字串形式的Html,該怎麼在React的JSX裡直接讀出我們要的內容呢?還好React非常的強大,它內建的屬性 dangerouslySetInnerHTML可以直接印出這段Html,直接來看程式碼:

<div dangerouslySetInnerHTML={{__html: 你想顯示的值}}></div>

這個屬性的名字取得很乾脆,直接提醒我們使用前要小心,小心甚麼呢?假如今天資料裡有不乾淨的程式碼很容易引起 cross-site scripting (XSS),這個問題就留給你們自己google,在使用前評估一下會不會有風險,我自己應用這個編輯器的地方是有設置權限的,所以就放心的使用了。

最後的最後再分享一個使用Next.js進行伺服器端渲染(Server Side Render)會遇到的問題,在import htmlToDraft和Editor時會報錯:Window is not defined,因為這個問題很值得另外寫一篇文章來討論(眼神死),所以這裡直接提供解法,直接看程式碼:

好的!照著做應該10分鐘可以搞定啦(心虛),坑都幫你們踩完了,推薦React Draft Wysiwyg這款所見及所得的文字編輯器給大家,有甚麼問題可以留言問我,雖然我很懶可能很久才會回。。。

希望這篇文章能幫助到大家,覺得有用不要吝嗇給我個clap呀(跪)!

Front-End Developer/Travel Guide Book Writer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store