実は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で扱いにくいのか？

最初に結論から言うと、<b>ReactはD3のように直接DOMを操作するのではなく、
仮想DOMを使って、変更分だけを更新します。</b>
そのため、D3のように直接DOMをいじるライブラリとは相性が悪いです。

Reactは仮想DOMという仕組みで変更分だけを更新するため、
直接DOMをゴリゴリいじるよりも描画が早いです。
古い記事ですが、[こちら](http://calendar.perfplanet.com/2013/diff/)が詳しく書かれています。

そのため、今までのようにD3を使ってしまうと、
Reactの良さが消されてしまいます。

もう1つ理由を上げるとすれば、
コンポーネントがあります。
D3はClassやIDを指定して直接DOMを操作するので、
画面に表示されているDOMであればなんでも操作ができます。

しかし、ReactはComponentを組み合わせてアプリケーションを作っていきます。基本的にComponentの外側のDOMは操作しません。
そのため、破壊的な操作ができません。

あれ、このDOMどこで操作しているんだと迷うことがありません。


コンポーネントについて図で見るとわかりやすいです。
下の図だと線で囲まれている5つの単位になります。

[![](https://facebook.github.io/react/img/blog/thinking-in-react-components.png
)](https://facebook.github.io/react/docs/thinking-in-react.html)


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


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

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

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

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

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

1. DOMの書き出しはD3を使わず、自分で書き、ScaleやColor、Max、Minなどの計算やデータ操作で利用する
1. 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編〜](http://ganeza.hatenablog.com/entry/2016/10/03/AWS%E3%81%AELambda%E4%B8%8A%E3%81%A7SVG%E3%82%92PNG%E7%94%BB%E5%83%8F%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%E3%80%9CPhantomjs%E7%B7%A8%E3%80%9C)

## アニメーションを実装しようとした時のつらみ

こちらおまけですが、
Reactを使った時に、アニメーションをどうやって実現するかというと、
2のDOMの直書きで実装した場合、
Stateを逐次変更し、再描画を繰り返すという方法があります。


こちらがStateを変更して逐次位置を変更させ、アニメーションを実装している例ですが、DOMの要素が少ない時はそれなりに動くのですが、
StackbarChartやScatterChartなどで要素数が多い場合だとカクついてしまいました。
なので、ここは改善の余地ありです。

https://swizec.com/blog/using-d3js-transitions-in-react/swizec/6797


別の方法として、
SVGのanimateタグを利用しReactのライフサイクルとは別にアニメーションを作ることもできるようです。

こちらの[react-loading](https://github.com/cezary/react-loading)が実際に利用している例です。

もちろん、1の方法で今までのやり方を使うこともできるかと思いますが、
Reactを使う以上は1以外の方法でどうにかしたいと思っています。

# まとめ
ReactでReactらしくD3を使うのはちょっとつらい。
でも、複数人での開発や、かなり作り込むようなアプリケーションを開発する際には、それでもReactは使った方がメンテのコストは下がるように感じました。

今回は日本語の説明だけだったので、
明日のアドベントカレンダーでバブルチャートをReactで描画する例をコードで説明していきたいと思います。

[D3.jsアドベントカレンダー2016](http://qiita.com/advent-calendar/2016/d3) はまだまだ空きがあるので、
よかったら登録してみてください〜