Draft.js概説：クールなエディタをアプリに導入しよう

# **Draft.js**使っていますか？
こんにちは！ samayottaです。

[Draft.js](https://draftjs.org/
)は、WYSIWYG リッチエディタを作成するためのフレームワークです。
Facebook謹製で、Immutable.jsに基づく設計(関数型言語の思想に基づく設計)になっているのが特長です。有名どころでは、WantedlyのエディタページはDraft.jsによって書かれています。
[Facebook謹製フレームワークDraft.js + React.jsでつくるリッチテキストエディタ](https://www.wantedly.com/companies/wantedly/post_articles/28285)
もしあなたがReactを書いていて、エディタ コンポーネントを求めているなら、必ず候補にあがる選択肢の一つでしょう。

しかしながら、多くのjsライブラリでそうであるように、Draft.jsについて書かれた日本語資料は多くなく、また自由度の高さゆえに抽象的でとっつきにくいところがあります。
そこでこの記事では、Draft.jsの公式デモ・プログラムの導入を行いその読解を手がかりとして取り扱いをお伝えできればと思います。特に取り扱いについては、「Draft.jsのミソ」である**editorState**と**contentState**オブジェクトの利用を中心に説明します。

この記事を読めば、Reactアプリケーションの中にDraft.jsコンポーネントを設置し、さらにはAPIを引きながらいろいろな操作を実装できるようになるはずです。

# Draft.jsの高機能エディタデモを動かす
まずは公式ページのexampleプログラムを実行してみましょう。  

```bash
$ git clone https://github.com/facebook/draft-js
```
リポジトリをcloneし、
`examples/draft0-10-0/rich/rich.html
`をブラウザで開くと下のようにエディタが出てきました。
![スクリーンショット 2018-12-24 14.37.04.png](https://qiita-image-store.s3.amazonaws.com/0/187851/04cfe9cf-80be-3d10-807c-269402ee59f3.png)
実際、このプログラムはとても良く動作します。まさにこのように動くエディタを実装したいですね。


# デモ・プログラムの読解
では、このエディタはどのようにして動いているのでしょうか？
[ソースコード](https://github.com/facebook/draft-js/blob/master/examples/draft-0-10-0/rich/rich.html)を追っていくと、次のような内容に目が行きます:

- 44行目 this.onChange  

```javascript
this.onChange = (editorState) => this.setState({editorState});
```
まず一番基本的なところから見ていきます。
`RichEditorExample`クラスはこのonChangeメソッドによってレンダリングが更新されます。このonChangeはReactユーザーにはお馴染みの記法で、いつものstrの代わりに`editorState`なるオブジェクトをセットしていることが読み取れますね。更新するときはこのオブジェクトをsetStateすれば良いようです。

- 113行目 InlineStyleControls コンポーネント

```jsx

<InlineStyleControls
  editorState={editorState}
  onToggle={this.toggleInlineStyle}
/>
```
このコンポーネントがエディタ上部の[Bold, Italic, Underline, Monospace]ボタンをレンダリングしています。クリックするとonToggleが実行されるのだろうと当たりをつけて読むと（実際そうなのですが）、`this._toggleInlineStyle`なる関数が中身だということが読み取れます。

``` jsx
  _toggleInlineStyle(inlineStyle) {
    this.onChange(
      RichUtils.toggleInlineStyle(
        this.props.editorState,
        inlineStyle
      )
    );
  }
```
この関数で注意したいことは２点です。

1. 関数`RichUtiles.toggleInlineStyle` 
**RichUtilsモジュール**というものがDraft.jsにあり、それを利用して、文字をボールドにしたりイタリックにしたりできるということがわかります。そしてthis.onChange()にその返値が渡されているということは、この関数の返値の型は`editorState`なのでしょう。

2. 引数`inlineStyle` 
ちょっとこのブログを読むのを止めて、この引数`inlineStyle`に何が渡ってくるかを調べてみてください。
ほんの少し入り組んでいますが、最終的には157行目 `this.props.onToggle(this.props.style);`に辿り着き、そしてthis.props.styleは変数`INLINE_STYLES`の`style`であることが読み取れるでしょう。つまり"BOLD"とか"ITALIC"とかいったstrが、この`inlineStyle`の中身になります。

ここまでの読解の結果、**`editorState`なるオブジェクトが重要そうだ**ということがわかってきました。
また、**関数`RichUtiles.toggleInlineStyle` はその`editorState`とstrを引数に取り、何かの変化を加え、`editorState`を返す関数**であることが読み取れます。

# 答え合わせ
さて、上の読解により何となく事態はつかめてきたように思います。それでは、実際のAPIを参照しましょう。
[Draft.js RichUtils toggleInlineStyle](https://draftjs.org/docs/api-reference-rich-utils#toggleinlinestyle)

```jsx

toggleInlineStyle(
  editorState: EditorState,
  inlineStyle: string
): EditorState
```
RichUtilsの中には、deleteやコードブロック、タブといった操作のためのユーティリティ関数が取りそろえられています。あなたがそのような操作を実行したかったら、これらの関数に現在の`editorState`を渡し、そして変更が加えられた`editorState`を受け取って、エディタにsetStateしてやればよいというわけです。
これであなたは基本的なDraft.jsによるエディタ作成の方法を身につけたことになります。お疲れ様でした！

＊このように`editorState`の参照透過性を維持しながらプログラミングを進められるのがDraft.jsの良さであり、冒頭で述べたImmutable.jsに基づく設計(関数型言語の思想に基づく設計)という言葉の意味かなあというのが僕の理解です。

# 実践編
それでは、満を持してDraft.jsプログラミングの実践に移りましょう。
いくつかのポイントについても追加的に解説します。
## 空のeditorStateを生成する

```jsx
import EditorState from 'draft-js';
const editorState = EditorState.createEmpty();
```
## strからeditorStateを生成する

```jsx

const editorState = EditorState.createWithContent(
      ContentState.createFromText("Hello Draft.js!")
    );
```
ここで一つ新しい登場人物`contentState`が出てきました。`contentState`はその名の通りエディタの内容の本質的に重要なコンテンツ、例えば本文や装飾、その範囲などについて記載したオブジェクトです。`editorState`はそれよりもメタな情報、たとえばUndo/Redoスタックなどを貯蔵しています。

## editorStateにstringを足し込む
```jsx
// origin
const editorState = EditorState.createWithContent(
      ContentState.createFromText("Hello Draft.js!")
    );
// add 
const newText = editorState.getCurrentContent().getPlainText() + "Good night.";
const newEditorState = EditorState.createWithContent(
      ContentState.createFromText(newText)
    );
```
Draft.jsでは、この`editorState`と`contentState`の行き来が重要になります。contentStateをeditorStateから抜き出すためのメソッドは[.getCurrentContent](https://draftjs.org/docs/api-reference-editor-state#getcurrentcontent)です。

この両者の関係は次のQiitaエントリに詳しいです。
[苦しんで覚えるDraft.js -リッチテキストエディタをシュッと作る-](https://qiita.com/YudaiTsukamoto/items/264f333e90a1edb818a3
)
## セーブロード実装
Firebaseにエディタの状態をセーブしたり、ロードしたりしましょう。

```jsx
import {EditorState, ContentState} from 'draft-js';
import {convertToRaw, convertFromRaw} from 'draft-js';
import {stateToHTML} from "draft-js-export-html";
```
### セーブメソッド 
```jsx
  saveText(title){
    const contentState = this.state.editorState.getCurrentContent();
    const content = convertToRaw(contentState);
    const html = stateToHTML(contentState);
   // 以下FireBaseの処理
    const textsRef = this.db.ref(this.savePointPath + "savedText/");
    const newTextRef = textsRef.push();
    return newTextRef.update(
      {title:title, time:Date.now(), content:JSON.stringify(content), html:html}
    );
  }
```
[draft-js-export-html](https://www.npmjs.com/package/draftjs-to-html)を使ってエディタの内容をHTML変換し、本体と一緒にpushしています。こういうものをpushしておくと、previewなどが簡単に実装できて便利です。parseするときは[html-react-parser](https://www.npmjs.com/package/html-react-parser) などが便利です。
### ロードメソッド
```jsx
  //引数contentはstringifyしたJSON文字列
  setEditorState(e, content){
    const contentState = convertFromRaw(JSON.parse(content));
    const editorState = EditorState.createWithContent(contentState);
    this.setState({editorState});
  }
```
StringifyしたcontentStateオブジェクトを、再びparseしてeditorStateにはめこみます。
あとはsetStateするだけでOKです。

## Draft.jsのひろがり
以上でこの記事の主要な内容は終わりです。お疲れ様でした！
今後Draft.jsの日本語記事が増え、より多くの知見が得られるのを私も楽しみにしています。

最後に、Draft.jsのプラグイン追加について触れておきます。
[DraftJS Plugins](https://www.draft-js-plugins.com/)
Facebook非公式ながら、DraftJS Pluginsのページにはハッシュタグ、絵文字、動かせる画像、ドラッグ＆ドロップなど、ブログ・ミニブログ制作に欠かせない機能がずらりと並んでいます。よりモダンな機能を求めるなら、このサイトも要チェックです。




# footnote
この記事は**ミクシィ2019新卒 advent calender2018**のために書かれたものです。
この前の記事はPhalanxさんの[Kaggle奮闘記~塩コンペ編~](http://phalanks.hatenablog.jp/entry/2018/12/23/195354)です。並み居る猛者を抑えての一位ということで、すごすぎですね……。自分もより精進したいと思います。

私の本業はいちおう機械学習erで、就職面接中もずっとそのような説明をしてきたのですが、ここにきてフロントエンド JavaScriptが生活の中心を占めるようになりました。[Urtica プロジェクト](http://www.kikin.kyoto-u.ac.jp/spec/2017/05.html)にかかりきりになるようになったからです。機械学習とはまったく違う世界のプログラミングで、始めた当初は困ることも多かったですが、何とかこなせるようになると、人間が触る部分の実装も興味深いなあという気持ちになりました。
今後も積極的に二足の草鞋を履いていく所存です。