タグに「React」を持つ
1〜1件目 / 1件
1

 React/Reduxでゲームエディタを作る時のメモ / magicien 

色々と脇道に逸れていたけれど、ゲームエディタを作り始めることにした。
フレームワークは勉強も兼ねて React/Redux を使ってみることにする。この選択が正しいかどうかは分からない。間違っていたら作り直せば良いので気楽に行こう。

以前Reactをほんの少し触ったことはあったけれど、存在意義も含めほぼ理解できずに終わったので、今回は1から勉強し直す。以下、困ったことリスト。回答とか感想とかは未来の自分に任せることにする。React/Reduxの正しい設計が知りたくて見に来た人は参考にしてはいけない。

モジュールのバージョンは?

とりあえず最新のものを使おう。React v15.6.1、Redux v3.7.2。それと、React Routerがバージョンによって大きく挙動が変わるみたい。現状最新の React Router v4.2.0 を使う。色々試した結果、react-routerを使わずに、redux-first-routerを使うことにした。モジュールは随時最新化していくことにする。

デザインをどうするか

色々探してみたけれど、結局Bootstrapを使う。というか、他に選択肢が見つからなかった。自由度も良い感じに制限されていて迷わなくて済むし、もうこれで良いんじゃないかな。デフォルトだとボタンの余白が大きいのが少し気になる。スマホ向けには良さそうだけど、PC向けにはもうちょっと小さくしたい。

各国語対応はどうするか

後になってi18n対応するのは大変そうなので、予め設計を考えておく。react-boilerplateがi18n対応していたので、それを参考に実装する。ソースコードに日本語を埋め込むのも微妙だし、デフォルトの文言は英語にしよう。英語用のメッセージ一覧は無くても良いけれど、他の言語に翻訳する時にメッセージ一覧があった方が便利なので用意する。というか自動生成したい。

ルーティングはどうするか

最初、react-routerを使っていたけれど、URLに応じてコンテンツを変更しようと考えたときに、redux-first-routerの方が楽だということに気づき、乗り換えることにした。そのページを表示する直前・直後に実行する処理も書けるし便利。
redux-first-routerを使うと、ページを移動時にURLに応じたactionがdispatchされるようになる。逆に、actionをdispatchすることでページを移動させることも可能。このactionを使ってURLとstoreをうまいこと同期させてあげれば、コンポーネント側でURLを気にしなくても良くなる。いちいちdispatchしなくてもurlのリンクを書いておけばそれに応じたactionが実行される。便利。
この変更によって、redux-sagaは要らなくなってしまった。

データのフェッチはどうするか

react-boilerplateredux-sagaを使っているので、それに倣ってみる。どうやらGeneratorで実現しているようだ。Sagaの定義はStore/Actionと一緒に置いておく感じかな。actionをdispatchする前にsagaをrunしておく必要があるけど、全ページで全sagaをrunするのは無駄かな。全部runするか、ページ毎に対象を指定するか、うまい方法が無いかは実装しながら考えよう。

今のbabelの設定だと、async/awaitが使えるので、そっちの方が良い気がしてきた。Sagaをrunするとactionの受け取り手が無駄に増えちゃったりするし。GeneratorでもPromiseでも使えるみたいなので、async/awaitに全て移行してしまおう。

テストはどうするか

boilerplateだと各jsファイルの横にtestsディレクトリを作ってテストを書いているようだ。モジュールはJestを使っている。この構成も倣ってみよう。使い方はmocha/chaiとほとんど同じかな?

Undo/Redoはどうするか

これも後から対応するの大変そうだから先に考えておきたい。
大部分のデータは、redux-undoで実現できそう。undo/redoしたい粒度でデータをhistory/futureにとっておく。問題はどういう粒度で何のデータを保存しておくか。エディタのメイン画面はマップエディタにしたいと考えていて、マップの地面の高低はheightmapのテクスチャという形で保存したいと考えている。
この場合、画像編集のデータをどう保存すれば良いのか。画像を丸ごと保存してしまう?グレースケールだから行けそうか...でも変更箇所の差分だけ取っておく方が良いよなぁ。それだとredux-undoの使い方にはマッチしない。Photoshopの実装がどうなっているか知りたい。DBみたいに適当なタイミングでチェックポイント(画像丸ごと保存)を作って、そこからredoが妥当かなぁ。よくよく考えたら、動画フォーマットもそんな感じだよね。でも実装がめちゃくちゃ複雑になりそう。→ Undo/RedoできるCanvasを作った

データの保存はどうするか

一時保存は localStorage IndexedDB へ、永続化はGithubへ、という感じにしたい。Github APIはまだ動作確認中。→GitHub Appsでリポジトリにファイル追加・更新
クライアント側で何を使うかも悩みどころ。IndexedDBはそのままでもシンプルかつ高機能なので、特にラッパー無しでもいけそうな予感。まさか仕様としてブラウザ上にDBが実装されるようになるとは。

ファイル構成をどうするか

Action/Reducer/Selector/Storeを1ファイルにまとめる

React/Reduxの実装例とか見ると、ほとんどの例でファイルが粉々に分割されていて非常に読み辛かった。なので、ducks-modular-reduxを参考に一ファイルにまとめてしまうことにした。テストするときに困るだろうか。

ContainerとグローバルのStateを分離する

ContainerとAction/Reducer/Stateが一対一で対応している例が多かったけれど、その構成のままContainerを100、200と増やしていくと地獄と化すのが目に見えているので、分離することにした。(実装例の規模が小さすぎて1組ずつしか無かっただけかもしれない)
Containerとデータ構造・操作をくっつけると再利用性が上がるのってReduxを使っていない場合の話だよね...?同じデータを複数のContainerで使う時にどう実装すべきか分からない。ContainerにStateを管理させると、設計がぐちゃぐちゃになって拡張が難しくなるようにしか思えない。

最上位のContainer(ページ)は別ディレクトリに分離する

Container同士にも親子関係があるので、同一ディレクトリにフラットに並べると分かりづらい。各画面の最上位のContainerは別ディレクトリに分けることにした。後でページ毎にwebpackするjsファイルを分けるのにも便利じゃないかと思う。

jsのファイル名はindex.jsにする

importするときにファイル名まで書かずに済むから楽なんじゃないかと。

ファイル構成(暫定)

app
├index.html HTMLの外枠。
├app.js  Storeの生成、Router、i18nの仕込み。
├pages  最上位のContainer(ページレイアウト)置き場。
│└pageA
│ ├index.js react-helmetでヘッダ上書き。必要に応じてconnectする。
│ ├Component.js そのページ専用のコンポーネントは一緒に置いてしまう。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  ├index.test.js index.jsのテスト
│  └Component.test.js Component.jsのテスト
├containers 各ページ共通で使えそうなパーツ置き場
│└containerA i18n用コンテナとか。
│ ├index.js React.Component等。必要に応じてconnectする。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  └index.test.js index.jsのテスト
├components 各ページ共通で使えそうなパーツ置き場
│└componentA ボタンとか。
│ ├index.js React.Componentか関数。connectしない。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  └index.test.js index.jsのテスト
├redux
│├configureStore.js Storeを生成。Middleware、Reducer取り込み。
│├reducers.js modulesからreducerを一括でロードする。
│├routesMap.js URLとpagesのマッピングを書く。
││       ページ毎のデータフェッチとかアクセス制御(誘導?)とかも書いておく。
│├modules ActionType/Action/Selector/Reducer置き場
││└moduleA
││ ├index.js 全部まとめて一ファイルに。reducerをexport defaultする。
││ └tests
││  └index.test.js index.jsのテスト
│└tests
│ ├configureStore.test.js 
│ └reducers.test.js
├translations
│├en.json 英語用メッセージ。自動生成したい。
│└ja.json 日本語用メッセージ
├config
│└index.js convictを使って説明とか型とか書いておく。
└tests
 └app.test.js app.jsのテスト

Code Splittingはどうするか

webpackするときに、ページ毎のjsと共通のjsをそれぞれ別ファイルにしたい。
React + Reduxを使った大規模商用サービスの開発」だと、require.ensureを使っていたけれど、例によってReactRouterの仕様変更によりそのままでは使えなかった。
でもReactRouterのページにCode Splittingの方法が書いてあったので、それをそのまま使うことにした。bundle loaderがうまいことやってくれるらしい。生成されるjsファイルは、0.[ハッシュ].chunk.js、1.[ハッシュ].chunk.js みたいになっていて、ページ数が増えると数字がずれたりする。上の資料だと、モジュールの並び順を制御してハッシュ値が変わらないよう工夫していたけれど、今回はそこまで神経質にしなくても良いかな。
react-routerを使うのをやめたので、代わりにuniversal-importを使うことにした。大してコード修正せずにそのまま乗り換えられたので一安心。

Stateに溜まったゴミはどうすればいいのか

画面遷移を続けていくと、Stateにゴミデータがどんどん溜まっていく。データの後処理はどうするのが良いのかな?これもReduxを使うが故の悩みだろうか。

ネット上の情報はいろんなバージョンのReactを使っていてよく分からない

バージョン間の差異が大きすぎる。どうしたらいいんでしょうか。とりあえず半年以上前の記事は話半分で読むことにする。

参考になったもの

React + Reduxを使った大規模商用サービスの開発

画面とかStateとか大量にある場合の設計の実例。実サービスとして作っただけあって、実践的。本格的に作り始める前にこの資料を見つけられて本当に良かった。性能測定はどうやっているのだろう。

ducks-modular-redux

action-types/actions/reducersを別々のファイルにしてもあんまり意味がないから一つのファイルにまとめちゃおうという提案。自分も同じ疑問を持っていたので、しっくりきた。

react-boilerplate

実際にReact/Reduxのアプリを作る上で最小限必要なものが一通り揃っている。i18n対応しているのが非常に素晴らしい。Herokuへのデプロイに対応しているのもポイント高い。React Router v3だったので、残念ながらそのまま使うことはできなかったけれど、大いに参考になった。

wp-calypso

WordPressのReact/Redux実装。大規模アプリの実例として参考になった。規模が大きすぎる気がしなくもない。ライセンスがGPLなので要注意。

2017/09/02(Sat) 18:32:24