Wantedlyでエンジニアをしている竹本です。主にこのブログを含むフィードを中心に開発をしています。
最近、フィードの記事編集画面のリニューアルを行ったので事例の簡単な紹介と、得られた知見を共有したいと思います。
先日の Meguro.es #4 でDreaft.jsについて発表したスライドはこちらになります。あわせて参照して下さい。
リニューアル対象の記事編集画面のざっくり紹介 Wantedlyのフィードの記事編集画面は以下の画像のような、いわゆるリッチテキストエディタです。
これは記事を書くユーザーのイメージと乖離した記事が公開されないように、ページ全体が 「編集している見た目=公開されるもの」 になっています。
開発スタックは、弊社の高松が公開している「 WantedlyにReact + Reduxを導入した話 」で言及している通り、ES2015, Babel, React, Redux, Immutable.js, CSS Modules, webpackというかなり先進的なスタックを採用しています。
また、なぜそのような技術スタックを選択したのか、その経緯は「なぜ React か? サービスを成長させるための技術選択」に詳しく書いてあります。
エディタ部分をまるっと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 を読み進めることをおすすめします。