抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
1314 文字
7 分
React-Redux ソースコードをざっと読む

概要#

まずは図をご覧ください:

React-Redux

エントリファイルで以下のモジュールがインポートされていることがわかります:

  • 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#

サブスクライブ/パブリッシュパターンを実装し、clearnotifysubscribe メソッドを提供します。これらはそれぞれ、現在のキューをクリアする、イベントをトリガーする、メソッドをサブスクライブするために使用されます。

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;
}

selectorstateprop を介して次の状態の props を計算し、接続されたコンポーネントに渡すために使用されます。 この関数は selector のラッパーであり、props の計算に加えて、前回の計算結果を記録します。

connectAdvanced(selectorFactory,{options})(WrappedComponent)#

connectAdvanced の主な機能は、渡された selectorFactoryoptions に基づいて、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 をベースに、selectorFactorymapStateToPropsFactoriesmapDispatchToPropsmapDispatchToPropsFactoriesconnectAdvancedoptions パラメータとして渡しているだけです。

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 に依存するかどうかを検出し、selectorFactoryprops の変更時に再呼び出しが必要かどうかを判断するために使用します。
  • 初回呼び出し時に、mapToProp の実行後に別の関数が返された場合、それを処理し、この返された関数を新しい mapToProps として後続の呼び出しを処理するために使用します。
  • 初回呼び出し時に、呼び出し結果がリテラルオブジェクトであるかどうかを検証します。これは、開発者の mapToProps 関数が有効な結果を返していない場合に警告できるようにするためです。

まとめ#

react-redux は、Redux を React にバインドするためのライブラリです。react-redux を使用しない場合でも、React 内で直接 Redux を使用することは可能ですが、非常に手間がかかります。store の変更を手動で監視し、コンポーネントの render メソッドを手動で実行する必要があります。 react-redux は、この作業を助けるための高階コンポーネント connect を提供し、いくつかの最適化も行っています。私たちが必要なのは、store を監視する必要があるコンポーネントに mapDispatchToPropsmapStateToProps を提供することだけです。

この記事は 2017年8月4日 に公開され、2017年8月4日 に最終更新されました。2985 日が経過しており、内容が古くなっている可能性があります。

React-Redux ソースコードをざっと読む
https://blog.kisnows.com/ja-JP/2017/08/04/react-redux-source-code-read/
作者
Kisnows
公開日
2017-08-04
ライセンス
CC BY-NC-ND 4.0