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として渡します。
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
ReactのContextによってstore
とstoreSubscription
を子要素に伝えます。
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
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のconnect
はcreateConnect()
をデフォルト引数によって呼び出した戻り値で、別の挙動をさせることも可能です。
ドキュメントにもあるように、デフォルトでは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
のほうです。
Connect
のrender()
を見ると、React Componentとして表示されるのはWrappedComponent
とわかります。
export const storeShape = PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
})
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)
}
あとがき
Provider
とconnect()
によってReactのContextを通してstoreを伝える大まかな構造を見ました。
次回はconnect()
の引数として渡したmapStateToProps
とmapDispatchToProps
をどう使っていくのか、詳細に読んでいく予定です。