Coffee, jQueryで書いていたElectronアプリをES6, React, Reduxで書き直した

ElectronベースのTwitterクライアント: Nocturn

ElectronでYoruFukurou風のTwitterクライアントを作った - k0kubun's blog の時にCoffeeScriptとjQueryで作っていたNocturnというTwitterクライアントがあり、これをES6, React, Reduxを使って書き直した。この記事ではその時に得た知見、感じた事を書いておく。

Nocturn

移行したスタックと移行時に感じたこと

あらかじめお断りしておくと、僕は普段はRubyでサーバサイドの実装や運用をやっている人であり、JavaScriptに関してはほぼ素人の意見なので、以下はReactとかRedux興味あるけどまだ触ったことないですみたいな人向けの内容になると思う。

CoffeeScript → ES6 移行

参考: 春からはじめるモダンJavaScript / ES2015 - Qiita

CoffeeScriptはオワコンか?

Sprocketsではv4からES6のサポートが入るんだけど、リリースされているSprockets 4はまだbeta2なので、Railsアプリ書いてる人からするとむしろES6がまだ始まってない。 けどCoffeeScriptの開発はもうあまり活発ではないし、普及すればコンパイルしなくても使えるようになるES6の方が未来が明るい感じがするので移行した。

decaf

decafでcoffeeのコードをES.next に書き換える - Qiita
自動変換が結構ちゃんと動く。よーし書き直すぞと思っても手で書き直してくのは大変で、今までCoffeeScriptで書いてきた資産が変換するだけで動いてくれるのはすごく助かる。これのおかげで移行は楽だった。

babel

babelとにかく最高で、めちゃくちゃ拡張性が高くできていて、プラグインを追加するだけでJavaScriptの文法を増やすことができる。例えば僕は引数のリストにケツカンマを書かないと気が済まない人間なんだけど、これはbabel-plugin-syntax-trailing-function-commasを入れると使える。便利。

言語自体に関しては、CoffeeScriptでインデントをミスって壊れるみたいなストレスを感じなくて済むあたりは嬉しいけど、21世紀にもなってセミコロン書くのが普通な言語だし、まあどっちもどっちかなという印象。

jQuery → React 移行

比較対象がちょっとおかしい気がするけど、ここではビューのレンダリングをjQueryで生DOM操作してやるのかReact使ってVirtual DOMでやるのかみたいな意図。React、そのうち流行が過ぎて使われなくなるでしょとか思って放置してたけど意外とそうでもない雰囲気なので触ってみた。

Reactのメリット

  • Componentを操作するインターフェースが限られるので、
    • あるビューがどこから操作されるのか予測しやすい
    • 結合が疎になり必然的に再利用性が高いコードが生まれる

Reactのデメリット

  • shouldComponentUpdate*1の保守コストが高い
    • ちゃんと書かないとパフォーマンスのボトルネックになりうる
    • ミスると、更新が必要でも描画されないなどの見つけにくいバグの原因になる

というのが一番印象が強かった。Reactで書くと保守性が高くなるみたいな空気を感じるけど、僕にはshouldComponentUpdateを保守するコストがそれを相殺するように感じた。

Reduxの導入

Fluxフレームワークには、客観的に見て一番普及していて信頼性の高いReduxを採用した。 なんか@mizchiがReduxめっちゃdisってるんだけど、Redux触るまではこのまとめで言われてることがほとんど理解できなかったので*2、とりあえず彼が言ってることを理解したいというのもあって触っていた。

Reduxの良いところ

  • Fluxを知らない初心者がとっつきやすい
    • 実装の指針がそこそこあるのでSPAの設計に不慣れな初心者にはありがたい
    • http://redux.js.org のドキュメントが親切だし、情報源も多い
  • react-reduxが良い
    • Reactの描画に関して自動でいい感じのチューニングをしてくれる
    • バケツリレーが不要になる

Reduxに感じた不満

  • ファイルの数が多くなるので疲れる
    • ComponentのロジックをContainerに剥がしてくのやると結構大変
    • 分割されたActionやReducerのファイルを追加する地味な作業が頻繁に発生する
  • storeのstateに基づいてdispatchを行うContainerを書くのが大変
    • mergePropsを書く必要があるんだけど、これ書くの辛い
  • react-reduxを使っていると描画以外の部分が重くなることがある(後述)

いろいろ不満はあるけど、Reduxを導入したことで、Fluxやってない時に僕が適当に考えた設計よりはどこで何が行なわれてるかわかりやすくなったと思う。

ReactとReduxのパフォーマンスの問題

ReactとReduxで書き直したらすごい遅くなった

フルスクラッチ後、キー入力によってツイートのフォーカスを移動したりすると画面がめちゃくちゃ固まるようになった。あと、textareaに文字を打ち込むだけでもかなり重くなった。

HipChatも以前Reactで書き直されたんだけど、Reactで書き直された現在のバージョンはタブの切り替えがめちゃくちゃ重くて、そのHipChatと同じくらい重くなった。

量が多いComponentでeventをsubscribeしてはいけない

いろいろプロファイリングをやっていたんだけどなかなか原因がわからなくて、以下のツイートをした時点ではまだ勘違いしていた。

実際にはdispatchより2段ネストしたところのsetStateにかかる時間が問題になっていた。TwitterクライアントだとツイートComponentの数はかなり多くなるわけだけど、そのツイートのComponent全てがReduxのstoreのstateの変化をsubscribeしていて、ツイートの数だけsetStateされるのが重かった。

これはreact-reduxのconnectというAPIを使ってContainerを作ると自動的に行なわれる処理なので、connectをやめないといけない。数が多いComponentに接続するContainerはこういうコードを書いてconnectなしで作るようにして回避した。

shouldComponentUpdate書くの辛い

connectshouldComponentUpdateだけでなくrenderの中でもいろいろ最適化をやってくれるので、connectをやめるなら自分でシビアにshouldComponentUpdateを書かないと描画が遅くなってしまう。

React化したHipChatはチャットにいるユーザーのオンライン状況が更新されなくなったりする問題もあって、多分どこかのshouldComponentUpdateがミスってて更新されてないんだろうなという感じがする。タブ切り替えの重さも含め、Reactのこの辛さがHipChatの品質を下げているような気がしているんだけど、最近のバージョンからユーザーのアイコンが表示されるようになったりとか機能が充実してきたのも確かで、Reactによって開発しやすくなったことによってこれが達成された可能性もあるし、一概に否定はできないなと思った。

結論

ReactやReduxは大規模なアプリケーションで開発速度を維持するのには一定の効果がありそうだけど、パフォーマンスが要求されるアプリケーションでの導入には慎重になった方が良い。

*1:そのComponentを再描画する必要があるかどうかの判定

*2:ちなみに今見ても30%くらいしかわからない