抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
1148 words
6 minutes
A Brief Analysis of React Source Code: `ReactBaseClasses`

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 consider this.state to be immutable.
  • There’s no guarantee that this.state will update immediately. This means calling this.state might still return the old state.
  • There’s no guarantee that setState will be called synchronously. Multiple setState calls might be batched and updated at once. If you need to perform an operation after a setState 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 on this.* of the component itself at that moment, because the callback function might be called after receiveProps but before shouldComponentUpdate, and at that time, the new state, props, and context have not yet been assigned to this.

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.

A Brief Analysis of React Source Code: `ReactBaseClasses`
https://blog.kisnows.com/en-US/2017/09/21/react-source-code-analyze-2/
Author
Kisnows
Published at
2017-09-21
License
CC BY-NC-ND 4.0