react-reduxのconnect()を図解する

ReactとReduxの勉強中です。react-reduxのconnect()の理解に時間がかかったので、同じように悩んでいる方に向けて図にして説明します。初心者向けの説明なので、正確さよりもわかりやすさを重視しています。

目次

Reduxの世界

まずはReduxの世界です。シーケンス図にするとこんな感じです。

f:id:yucatio:20180921135405p:plain

  • viewはユーザの動作(クリックなど)が発生したら、actionを作成してstoreに渡す(dispatchする)。
  • storeは状態(state)を保持している。dispatchが呼ばれると、storeはactionとstateをreducerに渡す。
  • reducerはactionとstateを受け取り、新たなstateを作成して返す。
  • stateが更新されるとviewに通知され、再描画される

Reactの世界

Reactの世界をツリーで表すとこんな感じです。

f:id:yucatio:20180921154929p:plain

  • ツリーの各ノードをコンポーネント(component)と呼ぶ。
  • コンポーネントは状態(state)を持っている。
  • コンポーネントは自身のstateのみを更新することができる。
  • stateが更新されると自動的に再描画される。
  • 親から子に値(props)を渡すことができる。
  • 親から子に渡す値には、関数も含まれる。
  • 親は子の状態を直接(例えば、childComponentA.state.isActiveのように)参照することはできない。
  • コンポーネントが親から渡された関数を呼び出すことによって、子の状態を親に伝えることができる。

ここで、親から子に渡される値(props)をざっくり分けると、以下の2種類になります。

データ型 用途
数値、文字列、真偽値、配列、もしくはそれらで構成されているオブジェクト 表示のために使用される
関数 コンポーネントのイベントを親コンポーネントに通知するために使用される

React-reduxの世界

Reduxでは、状態(state)は基本的にstoreが持ちます。そして、react-reduxの世界でも、stateはstoreが管理し、各コンポーネントはstateを(ほとんど)持ちません。

何もしない状態だと、reactとreduxの世界は分断されています。

f:id:yucatio:20180921171434p:plain

Reactの各コンポーネントは、

  • 表示のための値
  • stateを書き換える関数(dispatch)

が欲しいのですが、このままではstoreと分断されているために値の取得と変更が行えません。

そこで、connect()の登場です。connectはReactコンポーネントとstoreをつないでくれます。

ここでは、ParentComponentとChildComponentの間に、connect()を行うContainerInMiddleを入れます。ContainerInMiddleはstoreからstateとdispatchを受け取って、子コンポーネントに必要な値を渡します。

f:id:yucatio:20180921223513p:plain

connect()の引数

connect()の呼び出し方は以下のようになっていて、mapStateToPropsとmapDispatchToPropsという2つの関数を引数に取ります。

const ContainerInMiddle = connect(
  mapStateToProps,
  mapDispatchToProps
)(ChildComponent)

それぞれ以下の役割があります。

データ型 用途
mapStateToProps storeが持っているstateをpropsに入れて子コンポーネントに渡す
mapDispatchToProps dispatchを呼び出す関数をpropsに入れて子コンポーネントに渡す

ざっくりいうと、mapStateToPropsでは、描画のための値をstoreから持ってくる役割があって、mapDispatchToPropsは、”ユーザの動作があった時の関数”を渡す役割があります。

mapStateToPropsの実装は以下のようになります。

const mapStateToProps = state => ({
  todos: state.todos
})

state.todoの内容を、todoという名前で子コンポーネントに渡すよ、という意味です。このように書いておくと、子コンポーネント(上記の例ではChildComponent)でprops.todoで値が取得できるようになります。

mapDispatchToPropsの実装は以下のようになります。

const mapDispatchToProps = dispatch => ({
  handleClick: id => dispatch(someAction(id))
})

id => dispatch(someAction(id))という関数を、handleClickという名前で子コンポーネントに渡すよ、という意味です。このように書いておくと、子コンポーネント(上記の例ではChildComponent)でprops.handleClickで値が取得できるようになります。

まとめ

connect()でstoreとcomponentをつなぐイメージを文中で示しました。

react-reduxのconnect()は、stateとdispatch関数をpropsに入れて子コンポーネントに渡すという動作をします。これによって、stateの情報を取得したり書き換えたりできます。また、子コンポーネント側では、reduxを意識することなく実装ができます。

あとがき

coonect()の書き方は、理解できれば簡単ですが、記号がいっぱいの書き方なので、初めはとまどいました。慣れれば簡単なので、文中の図が理解の助けになれば幸いです。

シーケンス図はこちらを利用して作成しました。とても便利です。

sequencediagram.org