1
/
5

Facebook謹製フレームワークDraft.js + React.jsでつくるリッチテキストエディタ

Wantedlyでエンジニアをしている竹本です。主にこのブログを含むフィードを中心に開発をしています。

最近、フィードの記事編集画面のリニューアルを行ったので事例の簡単な紹介と、得られた知見を共有したいと思います。

先日のMeguro.es #4でDreaft.jsについて発表したスライドはこちらになります。あわせて参照して下さい。

リニューアル対象の記事編集画面のざっくり紹介

Wantedlyのフィードの記事編集画面は以下の画像のような、いわゆるリッチテキストエディタです。

これは記事を書くユーザーのイメージと乖離した記事が公開されないように、ページ全体が「編集している見た目=公開されるもの」になっています。


開発スタックは、弊社の高松が公開している「WantedlyにReact + Reduxを導入した話」で言及している通り、ES2015, Babel, React, Redux, Immutable.js, CSS Modules, webpackというかなり先進的なスタックを採用しています。

また、なぜそのような技術スタックを選択したのか、その経緯は「なぜ React か? サービスを成長させるための技術選択」に詳しく書いてあります。


なぜ React か? サービスを成長させるための技術選択 by Shimpei Takamatsu | Wantedly, Inc.
Wantedlyでは、React + Reduxを中心としたWebフロントエンドの技術スタックを導入しました。 モバイル版の会社フィードや、 このブログを書いているエディタ 、企業が使う候補者管理の画面などがこのスタックで実装されています。 導入したスタックの詳細や導入の理由、既存のRails環境への導入方法は以前発表した以下のスライドを参照ください。 ...
https://www.wantedly.com/companies/wantedly/post_articles/27340


エディタ部分をまるっとDraft.jsに置き換えた話

今回のリニューアル内容は、表向きには「より表現力豊かな記事作成ができるようになった」としていますが、実際には「エディタ部分を拡張性が高いものに置き換えたうえで、いろいろな装飾の追加やiframe埋め込みの機能を追加」ということをやっています。

そして、今回のリニューアルの肝はエディタ部分を拡張性が高いものに置き換えるというところにありました。

なぜ拡張性が低かったのか?

以前より記事編集画面は、React+Redux構成だったのですが、使っているライブラリの関係でリッチテキストエディタがHTMLを直接触る方式をとっていました。コードで示すと以下のような雰囲気になります。

class Editor extends Component {
  props() {
    super(props)
    this.state = {
      html: '',
    }
  }

  onChange(html) {
    this.setState(html)
  }

  render() {
    return <div>
      <OldEditor
        html={this.state.html}
        onChange={this.onChange.bind(this)}
      />
    </div>
  }
}

Reactの中でHTMLを直接触っている不毛な感じが伝わったでしょうか?

この形で実装していくと、HTMLを直接いじって実装していくので、新しい要素を追加していくたびに複雑性が高まっていき、保守や機能追加が困難になっていきます。

Draft.jsの採用

結論から言ってしまえば、リッチテキストエディタを構築するためにDraft.jsを採用しました。

Draft.jsはFacebookが公開しているOSSです。React.jsの中でリッチテキストエディタを構築するためのフレームワークと謳われています。

Draft.js is a framework for building rich text editors in React, powered by an immutable model and abstracting over cross-browser differences.

Draft.js makes it easy to build any type of rich text input, whether you're just looking to support a few inline text styles or building a complex text editor for composing long-form articles.

実際に使ってみましたが、(従来のリッチテキストエディタ開発とくらべて)本当に簡単で、短い開発期間で既存のエディタより優れたエディタを作ることが出来ました。


Draft.jsの拡張性

なぜ、Draft.jsを採用したことで拡張性が低い問題を解決出来たかを説明します。

Draft.jsではエディタの状態をEditorStateという一つのStateで管理しています。このStateの中にContentStateという形でエディタの中の文章も含まれています。

import React from ‘react’;
import {Editor, EditorState} from 'draft-js';

class MyEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {editorState: EditorState.createEmpty()};
    this.onChange = (editorState) => this.setState({editorState});
  }
  render() {
    return <Editor editorState={this.state.editorState} onChange={this.onChange} />;
  }
}

Immutableなモデルなので、生のJSONにした時のContentStateを以下の画像に示します。


これを表示すると、次のような表示になります。

見てわかると思いますが、JSONだけで文章+装飾の表現ができています。この構造があることで拡張性を担保することが出来ています。やったね。

以下のようなBlock, InlineStyle, Entityを使って文章の構造、装飾を作っていきます。


Block

HTMLのブロック要素に対応する要素。エディタの内容は複数のブロックから構成されます。typeを以下のように変更することで表示するタグを変えることが出来ます。

RichUtils.toggleBlockType(this.state.editorState, 'header-two')

EditorState自体が選択範囲の情報も持っているのでいい感じに解釈してブロックをいじってくれます。

また、ホワイトリスト制で設定することができるのでコピー&ペーストの際に意図しないタグの混入を防ぐことが出来ます。

InlineStyle

インラインの文字を装飾する要素で、offset, lengthでtextのどの範囲にスタイルを当てるかの情報を持っています。ブロックとほぼ同じ感覚で設定出来ます。

RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD')

Entity

文字、装飾以外の情報を持つ要素です。実際の情報はBlockではなくEntiyMapというオブジェクトで管理することになります。

Draft.jsの辛い部分

ここまで、Draft.jsのいい部分を説明してきましたが、辛い部分もあるので書いていきます

ー 使っている人が少なく、情報が不十分

ー APIドキュメントが簡潔な分、書いていないことは自分でコードを読む必要がある

ー マルチバイト文字環境で良くない挙動がある

ー 改行の挙動が微妙なので自分でいじったりする必要がある

辛い部分を挙げましたが、今までのリッチテキストエディタと比較すると大きな進歩です。

まとめ

Draft.jsは拡張性の高いリッチテキストエディタを構築するのに効果的なフレームワークです。React上でリッチテキストエディタを作っていくなら使うべきだと思います。

Draft.jsに興味を持った方はDraft.jsのQuickStartを読み進めることをおすすめします。

Wantedly, Inc.'s job postings
30 Likes
30 Likes

Weekly ranking

Show other rankings