この記事はQiitaのD3.jsのAdvent Calendar 2016年の1日目の記事です。
今年も残り1ヶ月ですね。
本当にあっという間でした。
QiitaにAdvent Calendarの通知が2日前くらいから来て、
いつも、そのままそっと、Qiitaをブラウザのタブごと閉じていました。
今、Advent Calendarに投稿しなきゃと思うことで、
年の瀬を感じております。
最近は業務でReactとD3を使った可視化系のサービスを開発しているのですが、
正直、相性悪いです。
そのため、D3のv4の話しもしたいですが、
今回はReactを使ったD3の話しをしようと思います。
なぜD3はReactで扱いにくいのか?
最初に結論から言うと、ReactはD3のように直接DOMを操作するのではなく、
仮想DOMを使って、変更分だけを更新します。
そのため、D3のように直接DOMをいじるライブラリとは相性が悪いです。
Reactは仮想DOMという仕組みで変更分だけを更新するため、
直接DOMをゴリゴリいじるよりも描画が早いです。
古い記事ですが、こちらが詳しく書かれています。
そのため、今までのようにD3を使ってしまうと、
Reactの良さが消されてしまいます。
もう1つ理由を上げるとすれば、
コンポーネントがあります。
D3はClassやIDを指定して直接DOMを操作するので、
画面に表示されているDOMであればなんでも操作ができます。
しかし、ReactはComponentを組み合わせてアプリケーションを作っていきます。基本的にComponentの外側のDOMは操作しません。
そのため、破壊的な操作ができません。
あれ、このDOMどこで操作しているんだと迷うことがありません。
コンポーネントについて図で見るとわかりやすいです。
下の図だと線で囲まれている5つの単位になります。
- FilterableProductTable (orange): contains the entirety of the example
- SearchBar (blue): receives all user input
- ProductTable (green): displays and filters the data collection based on user input
- ProductCategoryRow (turquoise): displays a heading for each category
- ProductRow (red): displays a row for each product
このように、コンポーネント毎に分けることで、
どの要素をどのファイルが編集しているのかがすぐにわかります。
これは直接DOM操作をしない恩恵でもあります。
D3のように直接ClassやIDを指定して変更を加えるとReactを使うメリットがなくなります。
どうやってD3を活用するのか?
方法としては以下の3つあるかと思います。
-
ReactのComponentDidMount時に直接DOMを書き出す
-
DOMの書き出しはD3を使わず、自分で書き、ScaleやColor、Max、Minなどの計算やデータ操作で利用する
-
jsdomを利用する ※未検証
1. ReactのComponentDidMount時に直接DOMを書き出す
最初にこちらですが、
Reactにはライフサイクルがあり、描画やデータの受け渡しのタイミングがあります。
※ Reactのライフサイクルについては以下の2つの記事がわかりやすいです。
http://qiita.com/koba04/items/66e9c5be8f2e31f28461
http://qiita.com/kawachi/items/092bfc281f88e3a6e456
ライフサイクルの中でComponentDidMount
はComponentがDOMツリーに追加された状態なので、Render時に<div className='app' />
以下のDOMを書いていた場合、すでにブラウザ上には1度描画されています。
そのため、d3.select('.app')
で参照することができます。
なので、このタイミングではD3は今までと同じように利用することが可能です。
ただ、どのようなDOMが書き出されるかはコード上からはわからない、かつ直接DOMを操作するため、仮想DOMのよさを活かせていません。
通常はStateの更新などを行います。
ただ、DOM操作ができるので、ここで普通にD3.jsを利用することができます。
更新処理はcomponentDidUpdate
が利用できます。
こちらの記事ではD3でDOMの書き出しをやっています。
http://nicolashery.com/integrating-d3js-visualizations-in-a-react-app/
2. DOMの書き出しはD3を使わず、自分で書き、ScaleやColor、Max、Minなどの計算やデータ操作で利用する
これはどういうことかというと、
今までs3.selectしてgタグをappendとかやっていたのを、
以下のような形で直接DOMとして書きます。
なので、D3がよしなに、xやy、transformしてくれていたのを自分で書く必要があります。
return (
<g></g>
);
これをやるには、一度d3.jsで書き出してみてから、
DOMやxやyの設定値を調べ、DOMの直書きをしなければなりません。
正直、茨の道です。
D3.jsがいままでやっていたのを、わざわざ自分でやらなければいけない。
めんどくさい道です。
ただ、私は今、このやり方でやっています。
直接DOMを操作しないことがReactを使う意味だと思っているので、
開発しているうちに、コードが増え、
そのファイルが何をやっているかわからなくなり、
メンテできなくなるよりは、ずっといいアプローチに思えたからです。
3. jsdomを利用する ※未検証
最後にjsdomを利用する方法ですが、
これはブラウザがなくてもDOMをシュミレートするもので、
テストやサーバ側でDOMを書き出し画像として出力する際などに利用します。
この方法はまだ検証していないので、
できるかわからないのですが、jsdomで仮想的にd3のDOMを書き出し、
そのDOMをReactでレンダリングできるんじゃない?なんて考えました。
これは別の記事で検証してみます。
こちらの記事でjsdomを使ってSVGをサーバ側でレンダリングしています。
AWSのLambda上でSVGをPNG画像に変換する〜Phantomjs編〜
アニメーションを実装しようとした時のつらみ
こちらおまけですが、
Reactを使った時に、アニメーションをどうやって実現するかというと、
2のDOMの直書きで実装した場合、
Stateを逐次変更し、再描画を繰り返すという方法があります。
こちらがStateを変更して逐次位置を変更させ、アニメーションを実装している例ですが、DOMの要素が少ない時はそれなりに動くのですが、
StackbarChartやScatterChartなどで要素数が多い場合だとカクついてしまいました。
なので、ここは改善の余地ありです。
別の方法として、
SVGのanimateタグを利用しReactのライフサイクルとは別にアニメーションを作ることもできるようです。
こちらのreact-loadingが実際に利用している例です。
もちろん、1の方法で今までのやり方を使うこともできるかと思いますが、
Reactを使う以上は1以外の方法でどうにかしたいと思っています。
まとめ
ReactでReactらしくD3を使うのはちょっとつらい。
でも、複数人での開発や、かなり作り込むようなアプリケーションを開発する際には、それでもReactは使った方がメンテのコストは下がるように感じました。
今回は日本語の説明だけだったので、
明日のアドベントカレンダーでバブルチャートをReactで描画する例をコードで説明していきたいと思います。
D3.jsアドベントカレンダー2016 はまだまだ空きがあるので、
よかったら登録してみてください〜