23
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-24

Draft.js使っていますか?

こんにちは! samayottaです。

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

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

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

Draft.jsの高機能エディタデモを動かす

まずは公式ページのexampleプログラムを実行してみましょう。

$ git clone https://github.com/facebook/draft-js

リポジトリをcloneし、
examples/draft0-10-0/rich/rich.html をブラウザで開くと下のようにエディタが出てきました。
スクリーンショット 2018-12-24 14.37.04.png
実際、このプログラムはとても良く動作します。まさにこのように動くエディタを実装したいですね。

デモ・プログラムの読解

では、このエディタはどのようにして動いているのでしょうか?
ソースコードを追っていくと、次のような内容に目が行きます:

  • 44行目 this.onChange
this.onChange = (editorState) => this.setState({editorState});

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

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

<InlineStyleControls
  editorState={editorState}
  onToggle={this.toggleInlineStyle}
/>

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

  _toggleInlineStyle(inlineStyle) {
    this.onChange(
      RichUtils.toggleInlineStyle(
        this.props.editorState,
        inlineStyle
      )
    );
  }

この関数で注意したいことは2点です。

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

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

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

答え合わせ

さて、上の読解により何となく事態はつかめてきたように思います。それでは、実際のAPIを参照しましょう。
Draft.js RichUtils toggleInlineStyle


toggleInlineStyle(
  editorState: EditorState,
  inlineStyle: string
): EditorState

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

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

実践編

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

空のeditorStateを生成する

import EditorState from 'draft-js';
const editorState = EditorState.createEmpty();

strからeditorStateを生成する


const editorState = EditorState.createWithContent(
      ContentState.createFromText("Hello Draft.js!")
    );

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

editorStateにstringを足し込む

// 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では、このeditorStatecontentStateの行き来が重要になります。contentStateをeditorStateから抜き出すためのメソッドは.getCurrentContentです。

この両者の関係は次のQiitaエントリに詳しいです。
苦しんで覚えるDraft.js -リッチテキストエディタをシュッと作る-

セーブロード実装

Firebaseにエディタの状態をセーブしたり、ロードしたりしましょう。

import {EditorState, ContentState} from 'draft-js';
import {convertToRaw, convertFromRaw} from 'draft-js';
import {stateToHTML} from "draft-js-export-html";

セーブメソッド

  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を使ってエディタの内容をHTML変換し、本体と一緒にpushしています。こういうものをpushしておくと、previewなどが簡単に実装できて便利です。parseするときはhtml-react-parser などが便利です。

ロードメソッド

  //引数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
Facebook非公式ながら、DraftJS Pluginsのページにはハッシュタグ、絵文字、動かせる画像、ドラッグ&ドロップなど、ブログ・ミニブログ制作に欠かせない機能がずらりと並んでいます。よりモダンな機能を求めるなら、このサイトも要チェックです。

footnote

この記事はミクシィ2019新卒 advent calender2018のために書かれたものです。
この前の記事はPhalanxさんのKaggle奮闘記~塩コンペ編~です。並み居る猛者を抑えての一位ということで、すごすぎですね……。自分もより精進したいと思います。

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

23
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?