この記事は、React.js Advent Calendar 2015の24日目の記事であり、
8月よりReactとReduxで既存システムの置き換えを行った、私の苦労話です。
簡単に対象のシステムを紹介するとこんな感じです。
- 約50機能のシステム
- PCブラウザで見ることが前提
- サーバサイドも置き換えをする(実装言語はGo)
- フロントもサーバサイドも自分がやる
この記事には以下の内容は含まれません。
- Reactの使い方
- Reduxの使い方
- すごくかっこいいこと
それでは全ての始まった8月より始めます。
8月
その前にReact/Reduxを選んだわけ
みなさんご存知の通り、React以外にも、UI向けのライブラリはいくつかあります。今回もReact以外にAngularJS、Angular2、Mithril、Riotが候補に上がっていました。
しかしAngularJSはAngular2が発表されてしまって今後に不安があること、Angular2はAngular2で、当時betaですらなかったことから、候補から外しました。
Mithril、Riotについては、コンパクトさに魅力はあったのですが、公式、非公式を合わせた情報量の差と「寄らば大樹の陰」の発想で、候補から外しました。
Reduxを選んだのは、当時のflux界では完全にReduxに流れがあったことと、ソースが短く非常に薄いという点を評価しました。
ただし、ReactにしろReduxにしろ、完全無欠の手段だとは全く思っていませんし、来年にはPolymerが天下を取っていても驚きはしません。
苦労したこと
- webpackを設定する
- ES2015に慣れる
- Reduxの使い方を覚える
webpackの設定を覚える
今回、ビルドにはreduxのexampleの中で使われていたという理由からwebpackを選びました。
そして、既存コードがcoffeescriptを使っていたことと、
自分自身ES2015よりcoffeescriptの方に馴染みの会ったことから、coffeescriptで書けば楽なのでは?と考えてcoffeescriptベースの設定を調べ始めました。結論としてはこれは完全なミスでした。
JSXをcoffeescriptで使えるようにするcjsxや、cjsxをwebpackで読めるようにするcjsx-loaderというライブラリもあり、
余裕ではと思っていたのですが、それぞれのライブラリの微妙なバージョンの組み合わせなのか、webpackがうまく動作してくれませんでした。
さらに「React/Redux関係のサンプルコードはES2015で書かれている場合が多い」ということが決定的な理由となり、coffeescriptをやめて、ES2015で書いていくことにしました。
今年の4月ぐらいはまだまだReact.createClass
な記事が多かった気がするのですが、気がつくと当然のようにES2015を使う記事が増えており、
coffeescriptを使う場合、一旦サンプルコードをcoffeescriptに変換する必要があり、完全に余計な手間になります。
React/Redux、ES2015、coffeescriptの全てに精通していれば良いのかもしれませんが、変換の過程でミスをする可能性もあり、
だったら「ES2015というAltJS」を覚えるほうが良いと判断しました。
ES2015に慣れる
上の節に書いたようにES2015を使うことになりました。ES2015と言ってもES5の構文が使えなくなるわけではないため、
何も考えずにいるとvar, let, constが入り乱れる謎のコードが量産されます。
ということでeslintでvarを禁止しました。そして...obj
やof
のような使いやすい機能から徐々に使うようにしました。
9月
朗報
9月に入ったあたりで、サポートブラウザからIE8を除外できました。やった!
苦労したこと
- フォルダ・ファイル構成の簡略化(計画)
- Reduxのstateの名前の統一
フォルダ・ファイル構成(計画)
最初に書いた通り、このシステムは約50機能ほどで構成されています。
この機能同士は比較的独立しているので、全てを内包したSPAを作るという方針は取らず、50個の小さいSPAを作る(機能間はa要素でリンクして移動)という方針にしました。
基本的なファイル配置としては、エントリポイント毎にフォルダを切って、各機能はその下で完結するようにしました。
以下のようなフォルダがたくさんあると思ってください。
hogeList/
hogeList.js # エントリポイント
actions/
hogeListActions.js
components/
hogeListBox.jsx
containers/
hogeListRoot.jsx
reducers/
index.js // combineReducers用
hogeListReducer.js
store/
hogeListConfigureStore.js
この配置自体に特に問題は無かったのですが、作っていくうちに各機能毎のファイルにhogeListAction.js
とかhogeListReducer.js
のように丁寧に名前をつけるのが、だんだん面倒になってきました。
今回の場合、ミニSPAを作るという方針なので、一つの機能あたりの構成ファイル数は少なく、多くの場合actionもreducerも下手するとcomponentでさえ1個ということがほとんどです。
ということで、以下のように基本となるファイルについては全部同じ名前にしました。
hogeList/
hogeList.js # エントリポイント
actions/
MainActions.js
components/
MainBox.jsx
containers/
Root.jsx
reducers/
index.js // combineReducers用
Main.js
store/
ConfigureStore.js
これにより面倒さは減りました。
念のためですが、actionやreducer、componentなどが複数になることを禁止しているわけではありません。
Reduxのstateの名前の統一
これも最初の頃は、丁寧に名前をつけていました。
// reducers/index.js
import {combineReducers} from "redux";
import Main from "./Main";
export default combineReducers({
Main,
});
// containers/Root.jsx
// ...略
function mapStateToProps(state) {
return {
value: state.Main.value,
changed: state.Main.hoge.changed,
};
}
// ...略
// conponents/MainBox.jsx
// ...略
render(){
const value = this.props.value;
}
// ...略
これはこれで良いのだと思いますが、やはりいちいち名前をつけるのが面倒です。
最終的にこんな風にしました。
// reducers/index.js
import {combineReducers} from "redux";
import Main from "./Main";
export default combineReducers({
Main,
});
// containers/Root.jsx
// ...略
function mapStateToProps(state) {
return {
...state
};
}
// ...略
// conponents/MainBox.jsx
// ...略
render(){
const main = this.props.Main;
}
// ...略
わかりにくいかもしれませんが、componentでstateを取り出す時this.props.Main
のように、
reducerの名前で取り出せています。これによりcomponentを作るときの手間が減りました。
特にreducerが複数になった時にこの方法は効果を発揮してくれています。
10月
苦労したこと
- なし?
なし?(実際には見て見ぬふり)
この記事は私の職場の日報を元に書いているのですが、10月はどちらかというとサーバサイドの方で苦労していたようです。
しかし10月にとても大きな出来事がありました。React 0.14のリリースです。9月にRCが出ていたので、
そろそろだろうなとは予想していました。
しかし0.14の正式版が出てからバージョンを上げればいいやとスルーしていたのです。
そして10月、正式版が出たわけです。そこで私は、周辺ライブラリが追いついたら、バージョンを上げようと、またしてもスルーしました。
完全にフラグです。
11月
苦労したこと
- Reactのバージョンを上げる 0.13 -> 0.14(これまで作ったものを全てimuutableにする)
Reactのバージョンを上げる 0.13 -> 0.14(これまで作ったものを全てimuutableにする)
11月がやってきました。気づくとReact 0.14.3がリリースされていました。
いつかはやらないとダメな事です。遂にバージョンアップに着手しました。
とは言っても、Reactの出すwarningには全て対応していたし、それほど難しい事もしていないので、大してトラブルは無いだろうと楽観的に考えていたのですが、package.jsonを書き換えてビルドし直したところ、全く動かなくなりました。
エラーメッセージもwarningもでてこないため、流石におかしいだろうと、ドキュメントを読み直したりconsole.log
を仕込みまくって動作を検証したところ、reducerでstateを変えてしまっている、つまりstateがimmutableで無かったというのが原因でした。
stateをimmutableにするには、幾つか方法がありますが、手っ取り早く解決するためにImmutable.jsを導入しました。
最初のうちは「javascriptでimmutableとか、何言ってんだ?」とか思ってましたが、JavaなどのMapやListに慣れていた自分にとっては、Objectをこねくり回すより、Immutable.jsのAPIに従った方が楽だったようで、すぐにImmutable.js万歳になりました。
とは言っても、Reactのcomponentで使う上では、obj[key]
で指定した方が楽な場合が多く、reducerではImmutable.jsを使いつつ、componentで使う場合には、toJS()
して、通常のObjectとしてアクセスするという方針にしました。
すでにかなりの数の機能を作成済みでしたが、immutableにしないことには前に進めないため、3日くらいかけてimmutable化して行きました。
これが8月からの作業で最も苦労した出来事だったのですが、もし仮にjQueryなどで直接DOMを操作する方式をとっていた場合、
同規模の修正が3日で終わるとはとても思えず、そういう意味ではReact/Reduxを選んだ価値を再確認する出来事でした。
12月
苦労したこと
- i18nライブラリの変更
i18nライブラリの変更
全面immutable化という大工事が終わり、Immuable.jsのおかげでむしろ作業が効率化していきましたが、
この時点で非常に不満の残る部分がi18nの対応でした。
実は9月ぐらいにreact-intlを導入していたのですが、これを利用する上でes.nextのdecorator構文を使う必要がありました。
しかし、react-intl自体、というよりIntl API自体がイマイチ使いにくく、これのためだけにdecoratorという不安定な仕様を使ってしまっていることが、ずっと引っかかっていました。
そしてbabel6は少なくとも現時点でdecorator構文をサポートしていないため、babelのバージョンを上げることが出来ないという問題が発生してしまいました。
このbabelの問題を確認して以降、もはや我慢の限界になり、react-intlの利用を止め、i18nextのラッパーを書くことにしました。
immutable化の時ほど時間はかからず、1日弱ぐらいで概ね終わりました。
babelのバージョンアップにはまだ着手していないので、おそらく来月の苦労話はbabelに絡んだ話になるでしょう。
まとめ
8月からReact/Reduxと付き合ってきて、一番苦労したのはimmutable化でしたが、これは私の情報収集不足によるもので、ある意味自業自得です。
これを除くと、最も大変だったのは8月のES2015を覚えつつwebpackのビルド設定をしたことです。
このwebpackの設定部分は他のプロジェクトでも応用できるので、今後、今回の組み合わせを使う上ではそのコストを減らすことができるでしょう(その前にbabel6対応をしないとですが)。
苦労はしましたが、現時点でReact/Reduxはそれほど悪い選択肢ではありません。
来年の今頃までベターな選択肢であるかは全く不明ですが、ReactあるいはJSXについては、Viewのstandardとして生きていそうな気がします。Reduxについては、もう少し別なものが出てきそうな予感がします。
個人的には、RxJSやCycle.jsをもう少しマイルドにしたものが欲しいです。
それとFlowのような型の仕組みがもう少しこなれてくると、それを前提とした仕組みが現れるような気もします(それがRx系列なのか?)。
この苦労話が誰かの何かの役に立ちますように。