Help us understand the problem. What is going on with this article?

転職黙示録 (2) connect関数分からん

読むだけRedux

 なんだかんだでちょっとしたツールでもGUIがあると便利だなぁと思ったりします. かといってOSネイティブなのはちょっとと思うとWebフロントエンドににバックエンドとなるわけです. 1 つまりSPAなのですが, Vue.js, Angular, Reactを昔にドキュメント読んで触ったわけですが, 結局Reactかなぁと思ってReduxのコードを読んでいたんですがContext APIで十分な気がしてきたりしました. すでにこの辺の記事は大量にあるわけですが, せっかくなので勉強したことを書いておこうと思ったわけです.

reduxとreact-redux

 Reduxといってもパッケージは二つに別れてたりします.

redux react-redux
reduxのストアなど reactとreduxをつなぐ

 つまりパッケージとしてはreduxとreact-reduxは別ものであるということを認識しておく必要があります. Reduxを単体で使うこともできるようです.

createStoreはクロージャである.

 Reduxの中心はStoreと呼ばれるものです. createStore関数で作られます.

index.js
const preloadedState = [];
const store = createStore(reducer, preloadedState);

 このstoreが何かというのはソースに当たった方が分かりやすいです. 主要な部分を抜き出すと以下のようになります.

createStore.js
export default function createStore(store, preloadedState) {
  // private variables
  let currentReducer = reducer
  let currentState = preloadedState

  function dispatch(action) {
    // update state here with reducer!!
  }

  // public functions
  const store = ({
    dispatch: dispatch
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

  return store
}

 公開されているのはstoreで定義されたオブジェクだけで, currentReducerとcurrentStateが内部の状態としてプライベートな変数となっているのがわかると思います. オブジェクト指向風にいうとインスタンスの内部状態を変更するためのメソッドをstoreとしてまとめて公開しているといことです. クラスとほぼ同じに見えてきたでしょうか?

dispatchでstateを更新する.

 この中で重要なのはdispatchという関数です. やってることはとても単純です. dispatchを呼び出して内部状態のcurrentStateを更新しているだけです. その際最初に渡したreducerを使うわけです. reducerはactionに応じて処理を切り替えるswitchを関数化したものです.

function dispatch(action) {

  // ・・・

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action) // *
  } finally {
    isDispatching = false
  }

  // ・・・

  return action
}

Reduxと言われるものが担うのは実はおおよそこれだけです.2

Reduxの仕事

  • stateを秘匿する
  • dispatchでアクションを決める
  • reducerで更新する

ReduxとReactの関係

 さてこれでアプリケーション・ステートを管理するstoreというものが作れました. こいつをルート・コンポーネントからシャンパン・タワーのごとく注ぎ込めば万事オッケー・・・とはなりません:cry:

 Storeから適切なstateとdispatchをReactのコンポーネントに渡してやらないといけません. これがreact-reduxパッケージが提供する役割です.

connect関数が返すのはHOCである.

両者を結びつけるには, 以下のような関数を定義してconnectという関数に渡してやります. その戻り値はまた関数でここには実際の表示を担うコンポーネントを渡してやります. これを定義しているのはSomeComponentという別のコンポーネントで, WrappedComponentにmapStateToPropsとmapDispatchToPropsで変換の方法を教えるだけのコンポーネントです.

SomeComponent.js
const mapStateToProps((state, ownProps)) => {
  valueName: state.active
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(someAction(args))
})

const wrapWithConnect = connect(mapStateToProps, mapDispatchToProps)
const Connect = wrapWithConnect(WrappedComponent)
// or 
// const Connect = connect(mapStateToProps, mapDispatchToProps)(PresentationalComponent)

 これで上位コンポーネントから渡されてきたstoreがSomeComponentに渡されるとmapStateToPropsとかmapDispatchToPropsの方法でWrappedComponentに渡されます. WrappedComponentではpropsを通じて,

  • props.valueName
  • props.onClick

 という感じで特にstoreがどうとかstateだdispatchだと考えなくても通常のコンポーネントの時と同じように扱えるわけです. これは既存のコンポーネントの修正を少なくできると思います. ここでSomeComponentをContainerコンポーネントと呼び, WrappedComponentをPresentatioanlコンポーネントとか呼ぶそうです.

connect関数分からん:no_good_tone1:3

 さてreact-reduxはconnect関数が中心です. だからなんとなくconnect関数を呼べば全部やってくれるわけですが, 中は結構めんどくさい感じになっています.

  1. connect関数の戻り値はconnectAdvanced関数を実行した結果でwrapWithConnect関数を返す.4
  2. connectAdvanced関数の第1引数はselectorFactory
  3. selectorFactoryの正体はfinalPropsSelectorFactory
  4. selectorFactory関数を実行するとimpureFinalPropsSelectorFactory関数かpureFinalPropsSelectorFactory関数が実行され同名のimpureFinalPropsSelector/pureFinalPropsSelector関数が戻る.

 この関係性を押さえた上で以下のような処理がconnectAdvanced関数で実行されているようです.

  1. connectAdvanced関数のヘルパー関数であるcreateChildSelector関数がstoreをもらいselectorFactoryを実行する.
  2. selectorFactoryはfinalPropsSelectorFactory関数の別名なのでimpureFinalPropsSelector/pureFinalPropsSelector関数が返ってくる.
  3. これらの関数がchildPropsSelectorとしてuseMemoでキャッシュされる.
  4. childPropsSelector関数を実行してpropsを取り出す. 多分actualChildPropsってやつ.
  5. actualChildPropsをWrappedComponentに渡す.

 要はselectorFactory(finalPropsSelectorFactory)関数が上手やってくれているようです. 名前から何かを選択する関数が生成されることは予測できそうですが, それがprops選択関数で以下のいずれかになるわけです.

  • impureFinalPropsSelector(mapStateToProps, mapDispatchToProps, mergeProps, dispatch)
  • pureFinalPropsSelector(mapStateToProps, mapDispatchToProps, mergeProps, dispatch)

finalという言葉がポイントです. そしてこの関数に上述のmapStateToPropsとかmapDispatchToPropsとかを渡してpropsの具体的な選択方法を与えることになります. connectAdvanced関数のソースを意味が分かるように整理するとこんな感じでしょうか?

function createChildSelector(store) {
  return selectorFactory(store.dispatch, selectorFactoryOptions)
}

const childPropsSelector = createChildSelector(store)

const props = childPropsSelector(store.getState(), wrapperProps)

<WrappedComponent {...props} />

SomeComponentの正体

 SomeComponentではconnect関数を使ってConnectコンポーネントを生成しました. これは何かという話を最後にしておきたいと思います. connectAdvanced関数内部ではHOC(Higher Order Component)というテクニックが使われて言います. 高階関数が関数を引数にしたり, 戻り値にしたりすることができる関数でした. 同様にHOCはコンポーネントを引数に取り, コンポーネントを返すことができるコンポーネントのようです.
 wrapWithConnect関数がそれですが, この関数の内部ではConnectFunction関数コンポーネント持っており, これにWrappedComponent包みます. 最終的にConnectというコンポーネントを戻します. これがSomeComponentの正体になります. Reactにreduxを導入すると随分遠大なことをやっている様に感じるわけですが,5 基本的にはコンポーネントをRedux用のコンポーネントで包み込むということをやっているだけということになります.

まとめ

  • stateはstoreの内部状態
  • dispatchはstoreの公開関数
  • stateの更新方法はreducerで教えてあげる
  • stateからpropsへの変換方法はmapStateToPropsで教えてあげる
  • dispatchからpropsへの変換方法はmapDispatchToPropsで教えてあげる
  • storeからpropsに変換するコンポーネントがある
  • connect関数分からん
  • connectAdvanced関数分からん

注釈


  1. これはこれで面倒なのですが, サーバーとの通信はWebフロントエンドとか関係なくバックエンドとの通信はするので適当UIならWeb系の技術を使った方が簡単にできそうな気がします. 

  2. 興味がある人はドキュメントで理解を深めてください. そして教えてください. 

  3. 分かる人は教えてください:bow_tone1: 

  4. 上のコードでconnectの戻り値をwrapWithConnect変数に渡しているのはこのためです. 

  5. 実際にかなり理解できない部分がまだ残っていますが:sweat: 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away