LoginSignup
8
6

More than 5 years have passed since last update.

Reduxをソースコードから理解する その2

Posted at

Reduxをソースコードから理解する その1の続きです。
reactと連携するときに使うreact-Redux (5.0.3: 1c714abc1763)のコードから、Storeとdispatch()の使い方を理解します。Redux本体より複雑かもしれません。

react-redux/api.md at master · reactjs/react-redux

Provider

Providerはreact-Reduxが提供するReact Componentです。
ReduxのStoreをReactから参照するために使います。Providerにはstoreをpropsとして渡します。

sample
ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

ReactのContextによってstorestoreSubscriptionを子要素に伝えます。
Contextの利点はデータをpropsとして明示的に渡さなくても子要素に伝えることができる点です。

In some cases, you want to pass data through the component tree without having to pass the props down manually at every level. You can do this directly in React with the powerful "context" API.
Context - React

ProviderはContextの親の実装になっています。

親で static childContextTypes = {...} と getChildContext() を実装する
子で static contextTypes = {...} を実装する
子で this.context は getChildContext の結果を参照できる
React の Context を使って Flux を実装する - Qiita

Provider.js
export default class Provider extends Component {
  getChildContext() {
    return { store: this.store, storeSubscription: null }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }

  render() {
    return Children.only(this.props.children)
  }
}

Provider.childContextTypes = {
  store: storeShape.isRequired,
  storeSubscription: subscriptionShape
}

Providerの子要素はユーザがContextを受け取れるように実装する必要があります。それはreact-Reduxのconnect()によって自前のReact Componentをラップすることで実現できます。

connect()

react-ReduxのconnectcreateConnect()をデフォルト引数によって呼び出した戻り値で、別の挙動をさせることも可能です。
ドキュメントにもあるように、デフォルトではconnectAdvanced()へのFacadeです。コードもバッサリ削れば見通しが良くなります。詳細は次回の予定です。

// createConnect with default args builds the 'official' connect behavior. Calling it with
// different options opens up some testing and extensibility scenarios
export function createConnect({
  connectHOC = connectAdvanced,
・・・
} = {}) {
  return function connect(
・・・
  ) {

    return connectHOC(
・・・
    })
  }
}

export default createConnect()

connectAdvanced()

Providerの子要素としてContextが受け取れるConnectで自前のComponentをラップします。
connectAdvanced()の戻り値、遡ると、connect()の戻り値はwrapWithConnect()です。つまりサンプルコードによくある、export default connect()(TodoApp)wrapWithConnect(TodoApp)となります。hoistStaticsはちょっとプロパティをコピーする程度なので、実質的な戻り値はConnectのほうです。
Connectrender()を見ると、React Componentとして表示されるのはWrappedComponentとわかります。

PropTypes.js
export const storeShape = PropTypes.shape({
  subscribe: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  getState: PropTypes.func.isRequired
})
connectAdvanced.js
export default function connectAdvanced(
・・・
) {
・・・
  const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  }

  return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {
・・・
      render() {
・・・
        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
      }
    }

    Connect.childContextTypes = childContextTypes

    return hoistStatics(Connect, WrappedComponent)
  }

あとがき

Providerconnect()によってReactのContextを通してstoreを伝える大まかな構造を見ました。
次回はconnect()の引数として渡したmapStateToPropsmapDispatchToPropsをどう使っていくのか、詳細に読んでいく予定です。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6