Imported Modules
var ReactNoopUpdateQueue = require("ReactNoopUpdateQueue");
var emptyObject = require("fbjs/lib/emptyObject");
var invariant = require("fbjs/lib/invariant");
var lowPriorityWarning = require("lowPriorityWarning");
Among these, ReactNoopUpdateQueue
is the default updater, used to provide enqueue operations for update
, replaceState
, and setState
. However, perhaps because it’s the default updater, it only provides the API and validates input parameters, but no actual functionality. For example:
enqueueSetState: function(
// 需要 render 的实例
publicInstance,
// 接下来要 merge 的 state
partialState,
// 可选参数,setState 组件 update 后的回调
callback,
// 可选参数,调用函数的的名字
callerName,
) {
warnNoop(publicInstance, 'setState');
},
The other modules are general utility modules, so we won’t go into detail about them.
Exported Objects
module.exports = {
Component: ReactComponent,
PureComponent: ReactPureComponent,
AsyncComponent: ReactAsyncComponent,
};
Let’s look at these Components one by one.
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 = {};
This is the constructor function used to create basic components. Here, refs
defaults to an empty object, and updater
defaults to the ReactNoopUpdateQueue
we mentioned earlier.
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");
};
The setState
method is added to it. The actual operation is to put the next state to be set into the update queue. The comments mention:
- Always use the
setState
method to change state; you should considerthis.state
to be immutable. - There’s no guarantee that
this.state
will update immediately. This means callingthis.state
might still return the old state. - There’s no guarantee that
setState
will be called synchronously. MultiplesetState
calls might be batched and updated at once. If you need to perform an operation after asetState
call completes, you can provide an optional callback function. - When a callback function is passed to
setState
, it will be called at some point in the future. It will receive the latest component parameters (state
,props
,context
). These parameters might differ from the values onthis.*
of the component itself at that moment, because the callback function might be called afterreceiveProps
but beforeshouldComponentUpdate
, and at that time, the newstate
,props
, andcontext
have not yet been assigned tothis
.
The core idea is that setState
calls are asynchronous. As seen in the code this.updater.enqueueSetState(this, partialState, callback, 'setState');
, it merely enqueues the state to be updated into the update queue, rather than directly updating the state.
forceUpdate
ReactComponent.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};
A method for forcing an update. This method does not trigger shouldComponentUpdate
, but it will call componentWillUpdate
and componentDidUpdate
normally.
Ensure that all DOM transaction operations have completed when calling this method.
You should only call this method when you know that a deep-level state of a component has changed, but setState
was not called.
Deprecated Methods
/**
* 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]);
}
}
}
Here we can see isMounted
and replaceState
. These two methods are officially considered deprecated APIs, and their use should be avoided as much as possible during development.
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;
This is a typical JavaScript inheritance implementation. As you can see, PureComponent
inherits from ReactComponent
. PureComponent
possesses all the properties and methods of ReactComponent
, and additionally has one more property:
pureComponentPrototype.isPureReactComponent = true;
This property will be used later in the component update section.
AsyncComponent
I haven’t used this component in my actual development work, so let’s take a look:
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;
};
Similar to PureComponent
, it also inherits from ReactComponent
. On top of that, it adds the unstable_isAsyncReactComponent
property and sets its render
method to directly return its children
. From the newly added property name, we can see that this is a feature component that has not yet stabilized. Based on its name and content, the initial judgment is that it’s used to create asynchronous components, using a React component as a placeholder first, and then rendering when children
are passed via props
. From the render
method directly returning children
, we can infer that this allows for components without unnecessary nested HTML tags.
Summary
This module defines three basic component classes used in React. Key takeaways are: 1. State should be considered immutable. All modifications to state must be done via setState
, not by directly assigning to this.state = ...
. 2. setState
is an asynchronous operation, so directly reading this.state
immediately after calling setState
might not yield the expected result; the value read could still be the old state.
However, the internal updater logic for components is not defined with specific execution details within this module. For instance, after calling setState
, the state to be updated is enqueued. What happens during this enqueue operation, how batch updates are performed, and when the state update is actually completed—these answers need to be sought in other modules.
Related Articles
- {% post_link react-source-code-analyze-1 React Source Code Analysis - Entry File %}
- {% post_link react-source-code-analyze-2 React Source Code Analysis - ReactBaseClasses %}
- {% post_link react-source-code-analyze-3 React Source Code Analysis - ReactChildren %}
- {% post_link react-source-code-analyze-4 React Source Code Analysis - ReactElement %}
- {% post_link react-source-code-analyze-5 React Source Code Analysis - onlyChildren %}
This article was published on September 21, 2017 and last updated on September 21, 2017, 2937 days ago. The content may be outdated.