抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
1922 文字
10 分
React ソースコードの概要分析 - `ReactBaseClasses`

インポートされたモジュール#

var ReactNoopUpdateQueue = require("ReactNoopUpdateQueue");

var emptyObject = require("fbjs/lib/emptyObject");
var invariant = require("fbjs/lib/invariant");
var lowPriorityWarning = require("lowPriorityWarning");

その中で、ReactNoopUpdateQueue はデフォルトの updater であり、updatereplaceStatesetState のキューイング操作を提供します。しかし、デフォルトの updater であるためか、API と引数の検証のみを提供し、実際の機能は提供していません。例えば:

enqueueSetState: function(
  // 需要 render 的实例
  publicInstance,
  // 接下来要 merge 的 state
  partialState,
  // 可选参数,setState 组件 update 后的回调
  callback,
  // 可选参数,调用函数的的名字
  callerName,
) {
  warnNoop(publicInstance, 'setState');
},

残りのいくつかのモジュールは汎用的な補助モジュールなので、ここでは詳しく説明しません。

エクスポートされるオブジェクト#

module.exports = {
  Component: ReactComponent,
  PureComponent: ReactPureComponent,
  AsyncComponent: ReactAsyncComponent,
};

これらのコンポーネントを順番に見ていきましょう。

ReactComponent#

/**
 * Base class helpers for the updating state of a component.
 */
function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.isReactComponent = {};

これは基本コンポーネントを作成するためのコンストラクタで、refs はデフォルトで空のオブジェクトであり、updater は上で述べた ReactNoopUpdateQueue がデフォルトで設定されています。

setState#

ReactComponent.prototype.setState = function (partialState, callback) {
  invariant(
    typeof partialState === "object" ||
      typeof partialState === "function" ||
      partialState == null,
    "setState(...): takes an object of state variables to update or a " +
      "function which returns an object of state variables.",
  );
  this.updater.enqueueSetState(this, partialState, callback, "setState");
};

これに setState メソッドを追加します。実際の操作は、次に設定する state を更新キューに入れることです。コメントには次のように記載されています。

  • state を変更するには常に setState メソッドを使用してください。this.state は不変であると考えるべきです。
  • this.state がすぐに更新されることは保証されません。つまり、this.state を呼び出しても古い state が取得される可能性があります。
  • setState が同期的に呼び出されることは保証されません。複数の setState の呼び出しが一度にバッチ更新される可能性があります。特定の setState の呼び出しが完了した後に操作を実行する必要がある場合は、オプションのコールバック関数を提供できます。
  • setState にコールバック関数が渡された場合、それは将来のある時点で呼び出されます。その際、最新のコンポーネントパラメータ (state, props, context) が使用されます。これらのパラメータは、その時点でのコンポーネント自身の this.* 上のパラメータ値とは異なる場合があります。これは、コールバック関数が receiveProps の後、shouldComponentUpdate の前に呼び出される可能性があり、その時点では新しい statepropscontext がまだ this に割り当てられていないためです。

その核心思想は、setState の呼び出しは非同期であるということです。コード this.updater.enqueueSetState(this, partialState, callback, 'setState'); からわかるように、更新する state を更新キューに入れているだけであり、直接 state を更新しているわけではありません。

forceUpdate#

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};

これは強制更新メソッドで、shouldComponentUpdate はトリガーしませんが、componentWillUpdatecomponentDidUpdate は通常通り呼び出します。 このメソッドを呼び出す際には、すべての DOM トランザクション操作が完了していることを確認する必要があります。 このメソッドは、コンポーネントの深層の state が変更されたことを知っているが、setState を呼び出していない場合にのみ呼び出す必要があります。

非推奨のメソッド#

/**
 * Deprecated APIs. These APIs used to exist on classic React classes but since
 * we would like to deprecate them, we're not going to move them over to this
 * modern base class. Instead, we define a getter that warns if it's accessed.
 */
if (__DEV__) {
  var deprecatedAPIs = {
    isMounted: [
      "isMounted",
      "Instead, make sure to clean up subscriptions and pending requests in " +
        "componentWillUnmount to prevent memory leaks.",
    ],
    replaceState: [
      "replaceState",
      "Refactor your code to use setState instead (see " +
        "https://github.com/facebook/react/issues/3236).",
    ],
  };
  var defineDeprecationWarning = function (methodName, info) {
    Object.defineProperty(ReactComponent.prototype, methodName, {
      get: function () {
        lowPriorityWarning(
          false,
          "%s(...) is deprecated in plain JavaScript React classes. %s",
          info[0],
          info[1],
        );
        return undefined;
      },
    });
  };
  for (var fnName in deprecatedAPIs) {
    if (deprecatedAPIs.hasOwnProperty(fnName)) {
      defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
    }
  }
}

ここで isMountedreplaceState の2つのメソッドが見られますが、これらは公式に非推奨となるAPIとされており、開発プロセスでは可能な限り使用を避けるべきです。

PureComponent#

function ReactPureComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

function ComponentDummy() {}
ComponentDummy.prototype = ReactComponent.prototype;
var pureComponentPrototype = (ReactPureComponent.prototype =
  new ComponentDummy());
pureComponentPrototype.constructor = ReactPureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, ReactComponent.prototype);
pureComponentPrototype.isPureReactComponent = true;

これは典型的なJavaScriptでの継承の実装方法で、PureComponentReactComponent を継承していることがわかります。PureComponentReactComponent のすべてのプロパティとメソッドを持ち、さらにその上に1つのプロパティが追加されています。

pureComponentPrototype.isPureReactComponent = true;

このプロパティは、後のコンポーネント更新部分で使われます。

AsyncComponent#

このコンポーネントは、私の実際の開発プロセスではまだ使用したことがありません。見てみましょう。

function ReactAsyncComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

var asyncComponentPrototype = (ReactAsyncComponent.prototype =
  new ComponentDummy());
asyncComponentPrototype.constructor = ReactAsyncComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(asyncComponentPrototype, ReactComponent.prototype);
asyncComponentPrototype.unstable_isAsyncReactComponent = true;
asyncComponentPrototype.render = function () {
  return this.props.children;
};

PureComponent と同様に、これも ReactComponent を継承しています。さらに unstable_isAsyncReactComponent プロパティが追加され、render メソッドは children を直接返すように設定されています。新しく追加されたプロパティ名からわかるように、これはまだ安定していない機能コンポーネントです。名前と内容から、非同期コンポーネントを作成するために使用され、まずReactコンポーネントでプレースホルダーを置き、propschildren が渡されたときにレンダリングを行うと推測されます。render メソッドが直接 return することから、余分なネストされたHTMLタグなしでコンポーネントを実装できると推測できます。

まとめ#

このモジュールでは、Reactで使われる3つの基本コンポーネントクラスが定義されています。注意すべき点は以下の通りです。1. State は不変 (immutable) であると見なすべきであり、state のすべての変更は setState を通じてのみ行うべきで、this.state = ** を使用して state を更新してはなりません。2. setState は非同期操作であるため、setState を呼び出した後に this.state から直接値を取得しても、期待する結果が得られない可能性があります。取得される結果は古い state のままである可能性があります。 一方、コンポーネント内部の updater ロジックについては、このモジュールでは具体的な実行ロジックが定義されていません。例えば、setState を呼び出した後に更新する state をキューに入れる操作が行われますが、この操作の過程で何が起こるのか、具体的にどのようにバッチ更新が行われるのか、そしていつ state の更新が完了するのかについては、他のモジュールから答えを探す必要があります。

関連記事#

  • {% post_link react-source-code-analyze-1 React ソースコード浅析 - エントリファイル %}
  • {% post_link react-source-code-analyze-2 React ソースコード浅析 - ReactBaseClasses %}
  • {% post_link react-source-code-analyze-3 React ソースコード浅析 - ReactChildren %}
  • {% post_link react-source-code-analyze-4 React ソースコード浅析 - ReactElement %}
  • {% post_link react-source-code-analyze-5 React ソースコード浅析 - onlyChildren %}

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

React ソースコードの概要分析 - `ReactBaseClasses`
https://blog.kisnows.com/ja-JP/2017/09/21/react-source-code-analyze-2/
作者
Kisnows
公開日
2017-09-21
ライセンス
CC BY-NC-ND 4.0