インポートされたモジュール
var ReactNoopUpdateQueue = require("ReactNoopUpdateQueue");
var emptyObject = require("fbjs/lib/emptyObject");
var invariant = require("fbjs/lib/invariant");
var lowPriorityWarning = require("lowPriorityWarning");
その中で、ReactNoopUpdateQueue
はデフォルトの updater であり、update
、replaceState
、setState
のキューイング操作を提供します。しかし、デフォルトの 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
の前に呼び出される可能性があり、その時点では新しいstate
、props
、context
がまだthis
に割り当てられていないためです。
その核心思想は、setState
の呼び出しは非同期であるということです。コード this.updater.enqueueSetState(this, partialState, callback, 'setState');
からわかるように、更新する state を更新キューに入れているだけであり、直接 state を更新しているわけではありません。
forceUpdate
ReactComponent.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};
これは強制更新メソッドで、shouldComponentUpdate
はトリガーしませんが、componentWillUpdate
と componentDidUpdate
は通常通り呼び出します。
このメソッドを呼び出す際には、すべての 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]);
}
}
}
ここで isMounted
と replaceState
の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での継承の実装方法で、PureComponent
が ReactComponent
を継承していることがわかります。PureComponent
は ReactComponent
のすべてのプロパティとメソッドを持ち、さらにその上に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コンポーネントでプレースホルダーを置き、props
に children
が渡されたときにレンダリングを行うと推測されます。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 日が経過しており、内容が古くなっている可能性があります。