Insight into details! react 16.8.6 source code Analysis-1 error reporting and warning

Keywords: React npm Attribute github

preface

As a front-end page and demand shredder, it can achieve limited improvement by repeating the same business requirements in daily work. In order to jump out of this mountain and open up a new vision, I suggest you read the source code of the top open source library on the market. This is a great opportunity to learn and master js language features (since the front-end has developed to the present, large-scale applications are highly dependent on the framework. Under normal circumstances, ordinary developers have no chance to touch the underlying language features), and it is also an opportunity to deeply understand the underlying thinking of the framework. Here I choose react as the first one. Many articles on the source code analysis of react are either too old, or only part of the code or pseudo code is intercepted. Here I will choose react version 16.8.6 as an example, starting from line 0, without missing any source details, and share my experience in the source code reading process with you. I hope to make progress together with you. The source code involved in this series of blog posts will be put in git warehouse and linked at the end of the article.

text

The files of react library are mainly divided into two parts: react-dom.development.js and react.development.js (the source file is selected here, not the production environment min.js First, let's look at the introduction of react DOM on npm:

This package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as react to npm

The function of this package is developed for react as the entry of DOM element and server-side renderer. It needs to be used together with react. It is bundled with react and distributed through npm together. In short, react-dom.js It mainly refers to the parts related to the real DOM in react (the renderers of the front and back end rendering, etc.), react.js It is the core source of react, including the creation of react elements (virtual DOM), cloning and other logic. Give us a shot first, react-dom.development.js Source code: 21429 lines, react.development.js Source code about 2000 lines, persimmon pick up soft pinch, we from react.development.js At first, gossip less, directly into the source part.

code analysis

  1. type definition
/** @license React v16.8.6
 * react.development.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';



if (process.env.NODE_ENV !== "production") {
  (function() {
'use strict';

var _assign = require('object-assign');
var checkPropTypes = require('prop-types/checkPropTypes');

// TODO: this is special because it gets imported during build.

var ReactVersion = '16.8.6';

//  Use Symbol to mark react like attributes. If there is no native Symbol or putty syntax, use a number to mark
// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
var hasSymbol = typeof Symbol === 'function' && Symbol.for;

//  Symbol.for  Use the string as the key to create a symbol. If there is one, use the previous one. Otherwise, create a new one
//  These are the internal APIs of react. You can check the unclear ones on the official website
var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;
var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;
var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;
var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;
var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;
var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace;

var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;
var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;
var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
//  Lazy load flag
var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;

//  Define symbol. If it is not supported, it will use the bottom-up scheme

//  Iterator returning symbol
var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
//  key of attribute
//  To be an iteratable object, an object must implement the@ @ iterator method, which means that the object must have a key of the@ @ iterator property, which can be passed through the constant Symbol.iterator Access this property
//  https://www.cnblogs.com/pengsn/p/12892954.html
var FAUX_ITERATOR_SYMBOL = '@@iterator';

Here is a brief introduction, react.js Each guide uses two public packages, object assign and prop types / checkproptypes. Object assign is mainly for compatibility with IE browser. After all, other mainstream green browsers and Node.js 4. All the above environments have realized the native Object.assignapi And prop types / checkproptypes are mainly used to implement attribute verification.
react uses Symbol to identify the built-in attributes in priority to ensure uniqueness. If it is not supported, it uses a 4-digit hexadecimal number to degrade.

  1. Get the iterator and define the underlying error reporting function
//  Functions that return iterators
function getIteratorFn(maybeIterable) {
  if (maybeIterable === null || typeof maybeIterable !== 'object') {
    return null;
  }
  //  Get the iterator of the passed in parameter or@ @ iterator
  var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
  if (typeof maybeIterator === 'function') {
    return maybeIterator;
  }
  return null;
}

//  Using invariant to judge the invariance of input parameter
//  Using a format similar to sprintf to process input parameters, the relevant information will be removed, but the function itself will be retained in the production environment
/**
 * Use invariant() to assert state which your program assumes to be true.
 *
 * Provide sprintf-style format (only %s is supported) and arguments
 * to provide information about what broke and what you were
 * expecting.
 *
 * The invariant message will be stripped in production, but the invariant
 * will remain to ensure logic does not differ in production.
 */

 // Validation format
var validateFormat = function () {};

{
  //  The format is required to process error information. If there is no format, the format can be understood as a string template
  validateFormat = function (format) {
    if (format === undefined) {
      throw new Error('invariant requires an error message argument');
    }
  };
}

function invariant(condition, format, a, b, c, d, e, f) {
  validateFormat(format);

  if (!condition) {
    var error = void 0;
    if (format === undefined) {
      //  If there is no format, an unformatted error is thrown
      //  There is a throw error in the compressed code. Please use the uncompressed development mode to obtain the complete error information
      error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
    } else {
      var args = [a, b, c, d, e, f];
      var argIndex = 0;
      //  The replace method returns the return value of the callback function for each match
      //  Generate error message
      error = new Error(format.replace(/%s/g, function () {
        return args[argIndex++];
      }));
      //  The wrong name is a violation of invariance
      error.name = 'Invariant Violation';
    }
    //  We don't care about the call stack of the error reporting function itself. Set the position to 1. Frame here can be understood as a frame in the call stack
    error.framesToPop = 1; // we don't care about invariant's own frame
    throw error;
  }
}

getIteratorFn is defined here. Where recursion and flattening operations are needed later, the iterator function of the object needs to be obtained. In essence, invariant is a throw function. According to the incoming template and corresponding parameters, the% s in the template is replaced in turn to generate the final error string.

  1. Define various warning functions
//  Depending on the implementation of invariant, we can keep the format and parameters in the construction of web
// Relying on the `invariant()` implementation lets us
// preserve the format and params in the www builds.

//  The warning implementation here is from fbjs/warning forked
//  The only difference we use console.warn instead of console.error When there is no native console implementation, we do nothing. This is the simplified code
//  This is very similar to the invariant. The only difference is that when the conditions are not met, we will print warning
//  This can print the absolute path of error reporting in the development environment. Removing log code in a production environment will maintain the same logic and maintain the same code path

/**
 * Forked from fbjs/warning:
 * https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
 *
 * Only change is we use console.warn instead of console.error,
 * and do nothing when 'console' is not supported.
 * This really simplifies the code.
 * ---
 * Similar to invariant but only logs a warning if the condition is not met.
 * This can be used to log issues in development environments in critical
 * paths. Removing the logging code for production environments will keep the
 * same logic and follow the same code paths.
 */

 // Low priority alarm
var lowPriorityWarning = function () {};

//  This bracket is used to close the context and keep the namespace clean
//  Can you avoid being covered in braces?
{
  var printWarning = function (format) {
    //  Copy into parameter group
    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    var argIndex = 0;
    //  Piecing together information
    var message = 'Warning: ' + format.replace(/%s/g, function () {
      return args[argIndex++];
    });
    //  If you have an implementation of console that uses it
    if (typeof console !== 'undefined') {
      console.warn(message);
    }
    //  Nothing
    try {
      // --- Welcome to debugging React ---
      // This error was thrown as a convenience so that you can use this stack
      // to find the callsite that caused this warning to fire.
      //  Easy to use call stack query
      //  For debugging react
      throw new Error(message);
    } catch (x) {}
  };


  lowPriorityWarning = function (condition, format) {
    //  No format information, throw wrong
    if (format === undefined) {
      throw new Error('`lowPriorityWarning(condition, format, ...args)` requires a warning ' + 'message argument');
    }
    if (!condition) {
      //  Copy array first
      for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
        args[_key2 - 2] = arguments[_key2];
      }
      //  Incoming formats and parameters
      printWarning.apply(undefined, [format].concat(args));
    }
  };
}

var lowPriorityWarning$1 = lowPriorityWarning;

/**
 * Similar to invariant but only logs a warning if the condition is not met.
 * This can be used to log issues in development environments in critical
 * paths. Removing the logging code for production environments will keep the
 * same logic and follow the same code paths.
 */

 // No call stack warning
var warningWithoutStack = function () {};

{
  warningWithoutStack = function (condition, format) {
    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
      args[_key - 2] = arguments[_key];
    }

    if (format === undefined) {
      //  Direct error reporting without format
      throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument');
    }
    if (args.length > 8) {
      // Check before the condition to catch violations early.
      throw new Error('warningWithoutStack() currently supports at most 8 arguments.');
    }
    //  If true is ignored directly
    if (condition) {
      return;
    }
    if (typeof console !== 'undefined') {
      //  Convert all input parameters to strings
      var argsWithFormat = args.map(function (item) {
        return '' + item;
      });
      //  Add warning header
      argsWithFormat.unshift('Warning: ' + format);

      // We intentionally don't use spread (or .apply) directly because it
      // breaks IE9: https://github.com/facebook/react/issues/13610
      //  call is not called directly because an error will be reported under ie9
      //  Manually throw warning
      //  The console method will automatically replace% s with the added subsequent parameters
      //  I can't understand this usage. Look at this article https://www.cnblogs.com/web-record/p/10477778.html
      Function.prototype.apply.call(console.error, console, argsWithFormat);
    }
    try {
      //  This is the place for react debugging. It will not work normally
      // --- Welcome to debugging React ---
      // This error was thrown as a convenience so that you can use this stack
      // to find the callsite that caused this warning to fire.
      var argIndex = 0;
      var message = 'Warning: ' + format.replace(/%s/g, function () {
        return args[argIndex++];
      });

      //  Manually throw an error
      throw new Error(message);
    } catch (x) {}
  };
}

//  Copy of warning function
var warningWithoutStack$1 = warningWithoutStack;

//  Whether the warning status of an unmounted component is updated
//  A map,key is the name of the internal method of the component
var didWarnStateUpdateForUnmountedComponent = {};

//  Warning of no OP no operate
function warnNoop(publicInstance, callerName) {
  {
    var _constructor = publicInstance.constructor;
    //  Get the name of the component
    var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
    //  Alarm key
    var warningKey = componentName + '.' + callerName;
    //  Updated or not
    if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
      return;
    }
    //  Call the method that throws the error and register at the same time to avoid secondary triggering
    //  You cannot call a method on an unmounted component. This is a meaningless operation,
    //  But this will trigger bug s in your application, so you can point directly to this.state Or define a state object with the class properties of the desired state in a component.
    warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);
    didWarnStateUpdateForUnmountedComponent[warningKey] = true;
  }
}

First, we define the bottom-level printing warning method, printWarning, which also outputs error information according to format, and then we use it to assemble lowPriorityWarning. Later, we define warningWithoutStack, which carries out some parameter verification, which is essentially a call console.error But to avoid bugs caused by variables being injected, Some Sao operations are used to avoid bugs (this level of public libraries generally have similar operations), followed by warnNoop, where the alarm results are recorded through didWarnStateUpdateForUnmountedComponent to avoid repeated alarms.

  1. Abstract api for updating no OP queue
/**
 * This is the abstract API for an update queue.
 */
//  Update Abstract api of no OP queue, queue of no operate
var ReactNoopUpdateQueue = {
  //  Check whether the composite component is mounted
  /**
   * Checks whether or not this composite component is mounted.
   * @param {ReactClass} publicInstance The instance we want to test.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
  //  Input parameter is component instance
  isMounted: function (publicInstance) {
    return false;
  },

  //  Force refresh if it cannot be updated during dom changes

  //  You may want to force a refresh of the state if you cannot use setState to update the state,
  //  forceUpdate cannot trigger shouldComponentUpdate, but it will trigger componentWillUpdate and componentDidUpdate
  /**
   * Forces an update. This should only be invoked when it is known with
   * certainty that we are **not** in a DOM transaction.
   *
   * You may want to call this when you know that some deeper aspect of the
   * component's state has changed but `setState` was not called.
   *
   * This will not invoke `shouldComponentUpdate`, but it will invoke
   * `componentWillUpdate` and `componentDidUpdate`.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {?function} callback Called after component is updated.
   * @param {?string} callerName name of the calling function in the public API.
   * @internal
   */
  //  Warnings for queue air conditioning
  enqueueForceUpdate: function (publicInstance, callback, callerName) {
    warnNoop(publicInstance, 'forceUpdate');
  },

  /**
   * Replaces all of the state. Always use this or `setState` to mutate state.
   * You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} completeState Next state.
   * @param {?function} callback Called after component is updated.
   * @param {?string} callerName name of the calling function in the public API.
   * @internal
   */
  //  Update all States, always use this or setState to change the state, and this.state As immutable
  enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
    warnNoop(publicInstance, 'replaceState');
  },

  //  Set a subset of the state. This provides the merge policy and does not support deep replication. Next stage: expose pending state or not use it in the merge stage
  /**
   * Sets a subset of the state. This only exists because _pendingState is
   * internal. This provides a merging strategy that is not available to deep
   * properties which is confusing. TODO: Expose pendingState or don't use it
   * during the merge.
   *
   * @param {ReactClass} publicInstance The instance that should rerender.
   * @param {object} partialState Next partial state to be merged with state.
   * @param {?function} callback Called after component is updated.
   * @param {?string} Name of the calling function in the public API.
   * @internal
   */
  enqueueSetState: function (publicInstance, partialState, callback, callerName) {
    warnNoop(publicInstance, 'setState');
  }
};

//  Define empty objects
var emptyObject = {};
//  Freeze up
{
  Object.freeze(emptyObject);
}

The abstract api for the update queue of react component is defined here. First, the instance object of react noopoupdatequeue is defined. There are isMounted,enqueueForceUpdate and other methods inside. The main purpose here is to give the definition of the method.

To be continued

ending

For the sake of space, the source code analysis of this article is over, and the links will be synchronized here when the next article is published. Any mistakes or omissions are welcome to discuss in the comment area. Please point out the improper translation of official notes~
Warehouse address:
React 16.8.3 source code annotation warehouse
Next:
Insight into details! react 16.8.6 source code analysis - 2 component construction and acquisition call stack

Posted by SBro on Sun, 21 Jun 2020 20:12:41 -0700