ReduxをつかってSPAを作ったので、知ったことを整理する。
祝日ですね。ぼくはいまだに無職なのであまり関係ありませんが。
どんなアプリ
slim-templateで作成したメモをタグ付けして閲覧できる、ブログから日付によるカテゴライズを抜いたようなメモアプリです。
あまりにも自分用な内容のものをまとめるためにつくりました。
閲覧画面
編集画面
Reduxを知った
Reduxはアプリケーションの状態(State)を管理する仕組みを提供します。
Stateを保持するStore、Stateの変更を行うReducer、Stateを変更する引き金や材料を運搬するAction、を明確に分けることにより秩序をもたらします。
Storeのdispatchメソッドの引数にActionを渡すこと(Dispatching an action)により、ReducerがStore内の状態を変更するのがおおまかな流れです。
Action
Actionは{}
で作成されるような、素のオブジェクトです。
すべてのActionはtypeプロパティを持ち、必要に応じて他のプロパティを設定します。
実装ではActionCreatorと呼ばれる、Actionを作成して返すメソッドを用意して、都度Actionを作成します。
Reducer
ReducerはState(あるいは担当するState内の1プロパティ)と、dispatchingされたActionを引数に取るメソッドです。
Actionの内容にかかわらず、dispatchされたActionは全てのReducerに届けられるので、Actionのtypeプロパティを見てなにをするか、あるいはしないのかがReducer内に書くことです。
Store
StoreはReduxの本体です。
作成時の引数として一連のReducerを取ります。
自身のdispatchメソッドの引数として受けとったActionを、作成時に引数としてとった全てのReducerに渡します。
1アプリケーションにつき、1Storeとされています。
Reduxとその他をつなぐ
Reduxは状態を保持、管理するための機構なので、ViewやRoutingは独自にやっていく必要があります。
ViewにはReactを使うのが一般的であり、連携が容易になるReact-Reduxが用意されているので、それを用います。
React-Reduxのconnectを使えば、StoreのStateとdispatchメソッドを、View最上位のReact.Componentのpropsとして簡単に注入できます。
秩序の維持
Storeの状態の変更には、dispatchメソッドとAction(ActionCreator)を知る必要があります。
Reduxでは、各コンポーネントがStoreを知り、独自にStoreにdispatchすることをよしとはしていません。
dispatchとActionを知るのはView最上位のReact.Componentのみにし、それより下位にはpropsとしてdispatchするためのメソッドを渡すのがよいとされています。
だがつらいバケツリレー
Stateからのpropsバケツリレーはつらくありません。データの構造にしたがってReact.Componentは組みたてられるため、不必要なデータというのはほとんどないからです。
しかし、データではないステート(Stateではなく、一般的なアプリケーションの状態)や、前述の下達されるメソッドは、リレー途中では必要がない場合があり、とても精神的につらくなってきます。
乱暴な解決
というわけで、今回はdispatchするメソッドが一堂に会したクラスをMixinの名のもとにReact.Componentに知らせ、そこでワンクッションおくことで一応Reduxは分離できてるぜということにしましたが、なかなかひどい。
ルーティングをどうやっていくか
シングルページアプリケーションではリロード時やURLでのリンク時の動作が課題となります。
今回、いわゆるGETメソッドによる動作は、React.Componentからでも(最終的には)pushStateするメソッドを使い、遷移には必ずRouterを通すことにより、処理を統一できました。
スクリーンショットのページを表示する際にはgo('/memo/6')
などとしpushStateでURLを書きかえ、Routerがプレイスホルダー変数を読み取り、あらためてdispatchするという流れです。
URL書きかえを遷移の基本としたため、あとで気がついたcanonicalの実装なども比較的楽に行えました。
他のライブラリとの連携
Codemirror
アドオンはグローバルにぶちまけるrequireになりましたが、普通に動きます。
Highlight.js
これはプロジェクト内でrequireとかimportする方法がわからず、cdsから読む従来の方法で使いました。
componentDidUpdateごとにinitしています。
TypeScript
React.Componentのpropsとstateの内容を厳密に決めておけるので、コンパイラの親切なエラー表示のおかげで、途中での改変がとても楽でした。
Actionのtypeプロパティにenumを使ったら綺麗なのでは?(Reducerではswitchを使ってるしね!)と思って使った結果、値としてはただのnumberなので、enumを分けた結果、お互いに同じ値を持つことになっていろんなReducerが大騒ぎになって反省したことだけは書いておかなくてはなりません。
結局下記のように個別にnumberを指定することになってなんのこっちゃみたいなことになりました。
export enum Tag{
Index = 1,
Pick = 2,
Select = 3
}
export enum Memo{
Index = 101,
Show = 102,
Remove = 103
}
string使いましょう。
Googleに関すること
これはReduxには関係ないのですが、Googleのボットは見ためはレンダリングしてくれるみたいですが、onClickでのリンク先まではなかなか見てくれないようです(要精査)。
なので、JavaScriptでは一切利用しないのでよくはないんですが、URIに対応したメモの内容をHTMLとして出力しています。(ソースを見ると適当なHTMLが出力されています)
トップページでは全てのメモへのリンクが埋められています。
こうしたページをGoogleに通知することにより、ようやくクロールが行われるようになった、というのが別のシングルページアプリケーションではありましたので、今回もそうしています。
ひととおり動くようになって
シングルページアプリケーションのあれこれを触りだしたのは無職になってからなので、まだ3-4ヶ月です。
なので他のフレームワーク事情などはあまり知らないのですが、関係を薄く保ち秩序を維持するという思想はわかる一方で、もっとベッタリ色々してくれてもいいのよ……という気持ちになりました。
最上位のpropsがズンドコ増えていく……
特に編集画面では大きな画面が3つ、ログイン、メモ一覧、メモ編集とあるのですが、それ用のprops羅列だけでもなんだがつらくなってきました。
辛くなった結果としてメソッドとステートを外に出したわけですが、Stateのネストなどもっと良い方法があったりするのでしょうか。
途中のpropsやStateの追加はつらいので計画が大切
あらためて書くようなことじゃなかったですね。