概要
まずは図をご覧ください:
エントリファイルで以下のモジュールがインポートされていることがわかります:
- Provider
- connectAdvanced
- connect
これらのモジュールを順に見ていきましょう。
Provider
store
をコンテキストにマウントし、子コンポーネントが store
にアクセスする機能を提供します。
connectAdvanced
connect
を生成するための高階関数で、以下のモジュールを参照しています:
import { Component, createElement } from "react";
import hoistStatics from "hoist-non-react-statics";
import invariant from "invariant";
import { storeShape, subscriptionShape } from "../utils/PropTypes";
import Subscription from "../utils/Subscription";
Subscription
まず、Subscription
モジュールを見てみましょう。
createListenerCollection
サブスクライブ/パブリッシュパターンを実装し、clear
、notify
、subscribe
メソッドを提供します。これらはそれぞれ、現在のキューをクリアする、イベントをトリガーする、メソッドをサブスクライブするために使用されます。
Subscription
サブスクリプションクラスで、onStateChange
イベントをサブスクライブします。同時に、親にもイベントサブスクリプションがある場合、イベントがトリガーされたときに、まず親イベントをトリガーし、次に現在のコンポーネントイベントをトリガーするようにします。
makeSelectorStateful(sourceSelector,store)
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props);
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true;
selector.props = nextProps;
selector.error = null;
}
} catch (error) {
selector.shouldComponentUpdate = true;
selector.error = error;
}
},
};
return selector;
}
selector
は state
と prop
を介して次の状態の props
を計算し、接続されたコンポーネントに渡すために使用されます。
この関数は selector
のラッパーであり、props
の計算に加えて、前回の計算結果を記録します。
connectAdvanced(selectorFactory,{options}) (WrappedComponent)
connectAdvanced
の主な機能は、渡された selectorFactory
と options
に基づいて、Connect
コンポーネントを生成するための高階コンポーネントを返すことです。
connect
class Connect extends Component {
constructor(props, context) {
super(props, context)
...
this.initSelector()
this.initSubscription()
}
...
initSelector() {
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}
initSubscription() {
if (!shouldHandleStateChanges) return
// parentSub's source should match where store came from: props vs. context. A component
// connected to the store via props shouldn't use subscription from context, or vice versa.
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
// the middle of the notification loop, where `this.subscription` will then be null. An
// extra null check every change can be avoided by copying the method onto `this` and then
// replacing it with a no-op on unmount. This can probably be avoided if Subscription's
// listeners logic is changed to not call listeners that have been unsubscribed in the
// middle of the notification loop.
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
...
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
connectAdvanced
内部で定義されたコンポーネントで、context
を介して store
のデータを取得し、initSelector
メソッドを介して初期 store
を含む selector
を初期化し、initSubscription
メソッドを介して store
の変更をサブスクライブします。
このコンポーネントの render
メソッドは単なるプロキシであり、selector
によって計算された props
をラップする必要があるコンポーネントに渡すために使用されます。
connect
connect
は以下のモジュールをインポートしています:
import connectAdvanced from "../components/connectAdvanced";
import shallowEqual from "../utils/shallowEqual";
import defaultMapDispatchToPropsFactories from "./mapDispatchToProps";
import defaultMapStateToPropsFactories from "./mapStateToProps";
import defaultMergePropsFactories from "./mergeProps";
import defaultSelectorFactory from "./selectorFactory";
これは connectAdvanced
をベースに、selectorFactory
と mapStateToPropsFactories
、mapDispatchToProps
、mapDispatchToPropsFactories
を connectAdvanced
の options
パラメータとして渡しているだけです。
defaultSelectorFactory
デフォルトの selector
の動作を定義し、渡された state
から次の状態の props
を計算するために使用されます。
defaultMap xxx To xxx Factories
これらのモジュールは、props メソッドにマッピングするファクトリ関数を生成するために使用され、そのコードは以下の形式です:
import { bindActionCreators } from "redux";
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from "./wrapMapToProps";
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === "function"
? wrapMapToPropsFunc(mapDispatchToProps, "mapDispatchToProps")
: undefined;
}
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant((dispatch) => ({ dispatch }))
: undefined;
}
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === "object"
? wrapMapToPropsConstant((dispatch) =>
bindActionCreators(mapDispatchToProps, dispatch),
)
: undefined;
}
ご覧のとおり、主に wrapMapToProps
モジュールを参照しています。
wrapMapToProps
wrapMapToPropsFunc
このメソッドは、mapToProps
関数をラップしてプロキシ関数として機能させ、以下のいくつかのことを行います:
mapToProps
関数の呼び出しがprops
に依存するかどうかを検出し、selectorFactory
がprops
の変更時に再呼び出しが必要かどうかを判断するために使用します。- 初回呼び出し時に、
mapToProp
の実行後に別の関数が返された場合、それを処理し、この返された関数を新しいmapToProps
として後続の呼び出しを処理するために使用します。 - 初回呼び出し時に、呼び出し結果がリテラルオブジェクトであるかどうかを検証します。これは、開発者の
mapToProps
関数が有効な結果を返していない場合に警告できるようにするためです。
まとめ
react-redux は、Redux を React にバインドするためのライブラリです。react-redux を使用しない場合でも、React 内で直接 Redux を使用することは可能ですが、非常に手間がかかります。store
の変更を手動で監視し、コンポーネントの render
メソッドを手動で実行する必要があります。
react-redux は、この作業を助けるための高階コンポーネント connect
を提供し、いくつかの最適化も行っています。私たちが必要なのは、store
を監視する必要があるコンポーネントに mapDispatchToProps
と mapStateToProps
を提供することだけです。
この記事は 2017年8月4日 に公開され、2017年8月4日 に最終更新されました。2985 日が経過しており、内容が古くなっている可能性があります。