http://redux.js.org/docs/basics/UsageWithReact.html のメモ
とあるソースコードを読んでいて、redux という仕組みを理解しないと何が起こっているか分からないので、仕方なしに redux も入門してみる事にした。いつまでたっても本題にたどり着けず辛い。。。
環境の作成には create-react-app を作る。
create-react-app react-test
cd react-test
yarn add redux
yarn add react-redux
Redux 用語
- Action: 状態を変えるためのコマンド (コマンドパターン)
- Reducer: 古い State と Action から新しい State を作る関数
- Store: action を受け取り Reducer を実行して State を返す。Store は reducer から作る。
- store.dispatch(action) で store に action を送る。
- createStore(reducer) を使って reducer から Store を生成する。
構成
もしかして他の react-redux プロジェクトでも使われるかも知れないのでディレクトリ構成を書く。
- components: 見た目だけの静的な Representational Component (カスタム HTML タグ) を置く。
- Component は props を受け取りタグを返す関数で表現する。このディレクトリに置くものは副作用が無い。
- containers: 入出力を含む動的な Container Component を置く。
- reducers: action を受け取り、新しい state を作成する。(Reducer とは酷い名前だ)
- actions: 入力フォームから action を生成する部分。別に分けなくても良い気がする。
Container (動的 Component)
React では、静的な Component と動的な Component を分けて書く事を推奨している。動的な物を Container Component と呼ぶ。
典型的には React-Redux では Container を実装するのに以下の二つの関数を用意する。
- mapDispatchToProps: Component のイベントを受け付ける。
- { event1: func1, event2, func2 } のような物を返すと、Component でイベント prop1 が起った時に func1 を実行する。
- func1 の中で action を生成し dispatch する。
- event1 は Component に props として渡される。
- mapStateToProps: state から Container の props を作る
- state と 自分の props を受け取る。
- state から作成する Component の props を作成する。
- 自分の props と state を比較して対象 Component の props を作成出来る。(選択中の要素をハイライトしたりするのに使う)
これらの関数を用意して、connect() で静的 component から Container を作る(デコレーションパターン)
connect(mapStateToProps, mapDispatchToProps)(Component)
作成した Container は以下のような動きをする。
- ユーザが静的 Component でイベントを起こす。
- Container の mapDispatchToProps が action を store に dispatch する。
- reducer が次の state を作る。
- Container の mapStateToProps が新しい state から props を作る、静的 Component
- React が props から新しい静的 Component を作る
見た目と機能の分割が難しく、Component の中にで直接 action を dispatch したい時は mapDispatchToProps や mapStateToProps を Container に書いてしまっても良い。その際は component()(Component) のようになる。
Provider
React-redux では、各 Component (アプリ独自の HTML タグ) で起ったイベントを一旦 store に通知して、store の中で全てのイベントを処理する。という事は、どの Component も store にアクセス出来るようにしなければいけない。これを実現するために、Provider という特別な Component をトップレベルに置く。
Provider 以下に作成された Component の props には dispatch 渡されるので、Component (または mapDispatchToProps) は dispatch を使って Store にアクセスする。
store は createStore(reducer) のようにして作る。
- reducer は action から state を作る関数なので、
- store は dispatch で action を受け取ると新しい state を作り、
- 多分 store.setState(新しい state) を呼ぶ
- 多分これが子供の state に伝わるのでは無いかと思う。どこかで store を subscribe しているようだがよくわからなかった。
combineReducers
お作法として、小さな Reducer を作って組み合わせて大きな Reducer を作る。分割の単位は State のツリーごとに行う。
const app = combineReducers({
node1: node1Reducer,
node2: node2Reducer,
})
のようにすると、node1Reducer と node2Reducer を順に実行して node1 と node2 を持つ reducer が出来る。
豆知識
- node 及び webpack では、import xx from './folder' のようにすると folder/index.js を読み込む。これがどの標準に基づくものか分からなかった。
react-redux で現在の state を見る。
もっとスマートな方法があるような気がするがよくわからなかった。。。react-redux のアプリには createStore で store を作っている部分があるので、グローバル変数に入れる。(Chrome と react-developer-tools を使うと、$r.store に store が入って便利です)
const store = createStore(todoApp)
Window.store = store
アプリを立ち上げて、console から JSON.stringify で state を見る。
JSON.stringify(Window.store.getState(), null, 2)
一旦 Window.store に入れると action を dispatch するテストも簡単に出来る。
Window.store.dispatch({type: "ADD_TODO", id: 5, text: "new todo"})