無職です。はたらいてます。 旧ブログ: 1 Entry per Day

React-Redux勉強メモ

近くReact+Reduxを触るのでとりあえずざっとQiitaの記事あたりを読んでいったメモ。


概要

react-reduxはreactの各コンポーネントのprops/stateの変更をreduxにすべて委譲してしまう仕組み。 reactは個々のコンポーネントの描画とボタン押下時にどのactionを実行するか(dispatch)だけに注力する。 reduxはdispatchされたactionによってStateをどう変更するかをreducerで管理する。 Stateの変更によるコンポーネントへの反映はreact-reduxのconnect関数とProviderによって実現する。

connectとかProvider

react-reduxの場合、Reactのコンポーネントを直接使わずconnectでwrapしたconnected componentを使う。 connected componentはactionを経由して行われたStateの変更を引き受けて再描画される。 正確に言うとそれだけでは動かなくて、Providerというreact-reduxのコンポーネントが親になっていてそこでStoreと繋ぐ。

const store = createStore(reducer)
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Action

Actionは単純なJavaScriptのオブジェクトに過ぎない。 typeプロパティを持つという制約があるだけ。↓のpayloadというは任意の値を受け渡す場合の例であって別のプロパティ名などでも問題ない。

{ type: "アクションを一意に示す任意の文字列", payload: "アクションの引数" }

Action Creator

typeの値の定数とかコンポーネントから渡される引数をpayloadにマッピング薄い関数をAction Creatorと呼ぶ。

function actFoo(arg) {return {type: ACT_FOO, payload: arg}}

dispatch

dispatchとはAction Creatorを呼びつつRedux側にActionを実行させる部分。 個々のコンポーネント毎にどのActionをdispatchするかは指定しておく必要がある。 その記述は今は結構雑に書けるそうだ。 ↓の場合、HogeComponentのpropsにactFooとactBarをdispatchするだけの同名の関数が渡されるので、コンポーネント内のclickイベントなどでそれを実行するようにしておくだけでよい。

export default connect(
    // mapStateToProps
    state => ({ value: state.value }),
    // mapDispatchToProps 
   { actFoo, actBar } // connectの第2引数にAction Creatorを纏めたオブジェクトを渡す
)(HogeComponent)

Container

実際には個々のコンポーネントごとにconnectしておくのではなく、ネストの親コンポーネントでだけconnectしておいたりすることがある。 それをContainerと呼ぶ。

StoreとReducerの分割

ReduxではStateを管理するStoreは基本的に1つだけにする。 そのStoreでactionを処理するReducerも1つにすることになる。 しかし、大きなアプリケーションだと1つのReducerになんでもかんでも突っ込むのはヤバイので、実際にはStateのプロパティごとにReducerを作り、それを纏めたReducerを使う。 その際は、分割した個々のReducerを自前で書いてcombineReducersというヘルパ関数でガッチャンコする。

function fooReducer(state = initialState, action){
    /* Stateのfooプロパティを扱うReducer */
}
function barReducer(state = initialState, action){
    /* Stateのbarプロパティを扱うReducer */
}
export default combineReducers({
    foo: fooReducer,
    bar: barReducer,
})

combineReducersを使わずに自前で分割するのも出来るっちゃ出来るがcombineReducers使った方が無難そう。

export default reducer(state = {}, action) {
  return {
    foo: fooReducer(state.foo, action),
    bar: barReducer(state.bar, action),
  }
}

Reducerは分割出来るがStateはあくまでもアプリ全体で1つだけであることを忘れずに。 上記の例ではReducer側ではfooやbarという単位で扱っているが、コンポーネント側ではあくまでfooもbarも持つState全体が渡ってくるので、mapStateToPropsでは気をつける。

参考文献