Coffee, jQueryで書いていたElectronアプリをES6, React, Reduxで書き直した
ElectronベースのTwitterクライアント: Nocturn
ElectronでYoruFukurou風のTwitterクライアントを作った - k0kubun's blog の時にCoffeeScriptとjQueryで作っていたNocturnというTwitterクライアントがあり、これをES6, React, Reduxを使って書き直した。この記事ではその時に得た知見、感じた事を書いておく。
移行したスタックと移行時に感じたこと
あらかじめお断りしておくと、僕は普段は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に文字を打ち込むだけでもかなり重くなった。
@k0kubun そうですね。フリーズするまででもないんですけど, かなりメモリ使ってるときと同じ感じですね。たとえば僕が mahoushoujomadokamagika と普通に打ってると2-5文字ぐらい一度に遅れて表示される感じです。
— 野宿労仂者 (@akawshi) 2016年3月12日
HipChatも以前Reactで書き直されたんだけど、Reactで書き直された現在のバージョンはタブの切り替えがめちゃくちゃ重くて、そのHipChatと同じくらい重くなった。
量が多いComponentでeventをsubscribeしてはいけない
いろいろプロファイリングをやっていたんだけどなかなか原因がわからなくて、以下のツイートをした時点ではまだ勘違いしていた。
keydownイベントの処理のほとんどの時間をreduxのdispatchが消費していて、reduxやめたら良いのではという気がしてきた pic.twitter.com/pe3q5B5Gho
— k0kubun (@k0kubun) 2016年3月12日
実際にはdispatch
より2段ネストしたところのsetState
にかかる時間が問題になっていた。TwitterクライアントだとツイートComponentの数はかなり多くなるわけだけど、そのツイートのComponent全てがReduxのstoreのstateの変化をsubscribeしていて、ツイートの数だけsetState
されるのが重かった。
これはreact-reduxのconnect
というAPIを使ってContainerを作ると自動的に行なわれる処理なので、connect
をやめないといけない。数が多いComponentに接続するContainerはこういうコードを書いてconnect
なしで作るようにして回避した。
shouldComponentUpdate書くの辛い
connect
はshouldComponentUpdate
だけでなくrender
の中でもいろいろ最適化をやってくれるので、connect
をやめるなら自分でシビアにshouldComponentUpdate
を書かないと描画が遅くなってしまう。
React化したHipChatはチャットにいるユーザーのオンライン状況が更新されなくなったりする問題もあって、多分どこかのshouldComponentUpdate
がミスってて更新されてないんだろうなという感じがする。タブ切り替えの重さも含め、Reactのこの辛さがHipChatの品質を下げているような気がしているんだけど、最近のバージョンからユーザーのアイコンが表示されるようになったりとか機能が充実してきたのも確かで、Reactによって開発しやすくなったことによってこれが達成された可能性もあるし、一概に否定はできないなと思った。
結論
ReactやReduxは大規模なアプリケーションで開発速度を維持するのには一定の効果がありそうだけど、パフォーマンスが要求されるアプリケーションでの導入には慎重になった方が良い。