search
LoginSignup
14
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

d3.js Advent Calendar 2016 Day 1

posted at

実はReactでD3は扱いにくい

この記事は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つの単位になります。

  1. FilterableProductTable (orange): contains the entirety of the example
  2. SearchBar (blue): receives all user input
  3. ProductTable (green): displays and filters the data collection based on user input
  4. ProductCategoryRow (turquoise): displays a heading for each category
  5. ProductRow (red): displays a row for each product

このように、コンポーネント毎に分けることで、
どの要素をどのファイルが編集しているのかがすぐにわかります。

これは直接DOM操作をしない恩恵でもあります。
D3のように直接ClassやIDを指定して変更を加えるとReactを使うメリットがなくなります。

どうやってD3を活用するのか?

方法としては以下の3つあるかと思います。

  1. ReactのComponentDidMount時に直接DOMを書き出す

  2. DOMの書き出しはD3を使わず、自分で書き、ScaleやColor、Max、Minなどの計算やデータ操作で利用する

  3. 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 はまだまだ空きがあるので、
よかったら登録してみてください〜

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
What you can do with signing up
14
Help us understand the problem. What are the problem?