facebook製のReact用エディタフレームワーク draft.js を触ってみた

5月ぐらいにdraft.jsを使って、Markdownエディタを作ってみた。普段技術ネタはBlogに書かないのだけど、まだあまり日本語情報も無いのでメモを公開してみる。 draft.jsはいまも活発に開発が進んでる状況なので、この数ヶ月で古くなってる部分があるかもしれません。

draft.jsとは何か

facebookが作っているReact用のリッチテキストエディタ (WYSIWYG) のフレームワーク。

facebookのnote機能や、メッセンジャーのエディタに使われてるらしく、今年に入ってオープンソースになった。ってことでまだあまり情報が無いが、react本家のfacebookが作ってるのもあり、エコシステムができつつある (後述) 。

draft.jsの特徴

draft.jsはあくまでフレームワークなので、そのままReactコンポーネントとして使えるものでは無い。draft.jsは、素のtextareaではなく、contenteditable属性を使ったdivをベースにする。

contenteditableはブラウザ毎の挙動の違いがあって、扱いが難しい(らしい)が、そこをうまく吸収しつつ、後述するいくつかの概念によってcontenteditable内を統一的に操作できるようなデータ構造を提供するのがdraft.jsの役割。

ということで、学習コストかかるものの、素のtextareaをゴリゴリやるよりは、保守性と拡張性を保った上でエディタが書けると使ってみて感じた。

draft.jsに登場する概念

だいたい https://facebook.github.io/draft-js/docs/api-reference-editor.html あたりにまとまってる。理解があってるかはまだあまり自信がない。

EditorState

エディタそのものの状態を表す。そのものというのは、入力されている内容物の状態はもちろん、キャレットの位置や、反転選択の状態、undo/redo のための変更履歴などを含めてエディタに対するほぼ全ての変更を指す。

ContentState

入力されている内容物の状態と、キャレットの状態を保持する。EditorStateの一部。内容物は、後述する幾つかのContentBlockから構成させる (これをblockMapと呼ぶ)。キャレットの状態は、SelectionStateで構成される。

ContentBlock

基本的にはエディタ内の、1行が1 Blockに相当する。ContentBlockcharacterListという、CharacterMetadataからなる配列を保持する。

CharacterMetadata

元々draft.jsはリッチテキストエディタ用なので、プレビュー欄みたいなのは無くて、例えば文字列をboldにしたらエディタ内で太字にする必要がある。 ここで、boldが太字であるという情報を持つのが、CharacterMetadata

Entity

さっきの例でいうと、太字というのが一つのEntityに相当する。Entityは、mutabilityという属性を持っていて、ここにIMMUTABLEという値をセットすると、その文字列 (Entity) はまとめて削除される。 例えば、メンション補完をして、入力した文字列を、バックスペースで一度に削除するみたいなことができる。

SelectionState

エディタ内のキャレットの状態を指す。

Key / Offset

Keyは、エディタ内のどのブロックにキャレットがいるかを示す。 Offsetは、ブロック内のどの位置にキャレットがいるかを示す。

Anchor / Focus

Anchorはキャレットを打ち込んだ位置。Focusは反転選択した際の終点。これにより、forwardで選択してるのか、backfowardで選択してるのかがわかる。

Modifier

ContentStateを操作するための、function郡。基本的にこれを通して、エディタの内容物に変更を加える。

draft.jsとMarkdown

結論からいうと、draft.jsはまだMarkdownをサポートしていない。

理想的には、リッチテキストエディタで視覚的にサポートされた状態で書いたMarkdownテキストを、Markdown記法でエクスポートされると良さそう (Preview欄が不要になる)。 react-rte というReact Componentがそれをやろうとしているが、実際使うとまだいまいちなとこはあって、もう少しdraft.js側でMarkdownのことを考慮されるとよさそう (計画はあるみたい)

ということで、自分が作ったエディタでは、EditorStateにプレーンテキストを出力させて、それをMarkdownとしてレンダリングするようにしている。そして、Markdownの入力をサポートするためのModifierを自前で書くことにした。

draft.jsのエコシステム

draft.jsをプラガブルに拡張した、draft-js-plugins というものがある。メンション補完や絵文字補完は、これを通して実現できた。

その他にもプラグインや、draft.jsを使ったReact Componentがどんどん出てきてる。有名っぽいのは、 https://github.com/nikgraf/awesome-draft-js にまとまってる。

おわり

draft.jsがリリースされたのが2016年2月末とかなので、まだまだこれからな感じはするけど、勢いがある。職業柄エディタは身近なものなので、つい自分の考えた最強のエディタを作りたくなるけど、実際やってみると改善点が山程あってエディタ沼にはまって出られなくなる。フレームワークに乗っかることで本質的な部分だけに集中して早く沼を抜けられるようになるといいな。