Reading the source code became one of my learning goals this year. Between Vue and React, I wanted to read React first. In deciding which version to read, I thought it might be easier to get in touch with the early ideas of the source code, and I ended up reading 0.3-stable. So next, I will interpret the source code of this version from several aspects.

  1. React render HTML elements
  2. React source code learning (b) : HTML child render
  3. React source code learn (3) : CSS style and DOM properties
  4. React source learning (4) : transaction mechanism
  5. React source code learning (5) : event mechanism
  6. React source code learning (6) : Component rendering
  7. React source code learning (7) : life cycle
  8. React source code learn (8) : Component update
  9. React source code learn (9) :
  10. React source learning (10) : Fiber
  11. React source code (11) : Scheduling
  12. React: Reconciliation

React.DOM.*

Getting straight to the point, in the official example you can see that the render function returns something like this:

// #examples
return React.DOM.h1(null.'Zong is learning the source code of React.')
Copy the code

Type =”text/ JSX “:

/** @jsx React.DOM */
// #examples
return <h1>Zong is learning the source code of React.</h1>
Copy the code

JSXTransformer. Js converts the form type=”text/ JSX “to the function form react.dom.h1.

With the react. renderComponent to render the

tag under the specified element, this code may render into the DOM as follows:

<h1 id=".reactRoot[0]">Zong is learning the source code of React.</h1>
Copy the code

How to render HTML elements

How does React render HTML elements?

The factory function objmapKeyval.js

ObjMapKeyVal is a factory function that eventually returns a “key” and the object “value” corresponding to obj is the result of func execution.

// utils/objMapKeyVal.js
function objMapKeyVal(obj, func, context) {
  if(! obj) {return null;
  }
  var i = 0;
  var ret = {};
  for (var key in obj) {
    if(obj.hasOwnProperty(key)) { ret[key] = func.call(context, key, obj[key], i++); }}return ret;
}
Copy the code

Create the React.DOM.* method

// core/ReactDOM.js
/** * The class used to create the DOM component, whose prototype connects to ReactNativeComponent */
function createDOMComponentClass(tag, omitClose) {
  var Constructor = function(initialProps, children) {
    this.construct(initialProps, children);
  };

  Constructor.prototype = new ReactNativeComponent(tag, omitClose);
  Constructor.prototype.constructor = Constructor;

  return function(props, children) {
    return new Constructor(props, children);
  };
}

var ReactDOM = objMapKeyVal({
  // ...
  // Danger: this gets monkeypatched! See ReactDOMForm for more info.
  form: false.img: true.// ...
}, createDOMComponentClass);
Copy the code

The “keys” in the ReactDOM object do not contain all the current HTML elements; if you need to add new HTML elements, you can add them to this object. However, if you want to support both type=”text/ JSX “, you will need to add this to JSXTransformer.

After the objMapKeyVal factory function is executed, the ReactDOM returns a value that is no longer a Boolean value. The key value takes the form of tag and omitClose and is instantiated by ReactNativeComponent. Returns a function that takes the props, children argument to instantiate this Constructor.

This example accepts props = null, children = ‘Zong is learning the source code of React.’ and instantiates Constructor.

Prototype React native component

Of course, it’s also worth mentioning what ReactNativeComponent instantiates:

// core/ReactNativeComponent.js
function ReactNativeComponent(tag, omitClose) {
  this._tagOpen = '<' + tag + ' ';
  this._tagClose = omitClose ? ' ' : '< /' + tag + '>';
  this.tagName = tag.toUpperCase();
}
Copy the code

For example, form: false returns the following object:

ReactNativeComponent {
  "_tagOpen": "<form ",
  "_tagClose": "</form>",
  "tagName": "FORM",
}
Copy the code

Utility functions – Mix

Is that all there is? Of course not, you need to notice a few lines at the end of reactnativecomponent.js:

// utils/mixInto.js
/** * Simply copies properties to the prototype. */
var mixInto = function(constructor, methodBag) {
  var methodName;
  for (methodName in methodBag) {
    if(! methodBag.hasOwnProperty(methodName)) {continue;
    }
    constructor.prototype[methodName] = methodBag[methodName]; }};Copy the code

Here, in turn, mix three objects to ReactNativeComponent. The prototype.

// core/ReactNativeComponent.js
mixInto(ReactNativeComponent, ReactComponent.Mixin);
mixInto(ReactNativeComponent, ReactNativeComponent.Mixin);
mixInto(ReactNativeComponent, ReactMultiChild.Mixin);
Copy the code

MixInto method is the function of the Object. The function of the assign, at the top of the code can also be written like this: the Object, the assign (ReactNativeComponent. Prototype, ReactComponent. Mixin).

So, let’s take a look at what this. Construct (initialProps, children) is doing here. Look backwards and find this method in reactComponent.mixin.

// core/ReactComponent.js
var ReactComponent = {
  Mixin: {
    construct: function(initialProps, children) {
      this.props = initialProps || {};
      if (typeofchildren ! = ='undefined') {
        this.props.children = children;
      }
      // Record the component responsible for creating this component.
      this.props[OWNER] = ReactCurrentOwner.current;
      // All components start unmounted.
      this._lifeCycleState = ComponentLifeCycle.UNMOUNTED; }}},Copy the code

Mount the React component instance to the DOM

At this point, how do we render this instance into the DOM? Let’s look at this code:

// #examples
React.renderComponent(
  React.DOM.h1(null.'Zong is learning the source code of React.'),
  document.getElementById('container'));Copy the code

Register React instances

The React. RenderComponent method renders the instance to the DOM. Let’s see what it does, ignoring the rest of the logic (component update/event registration) :

// core/ReactMount.js
// Count the number of public mounts
var globalMountPointCounter = 0;

/** Mapping from reactRoot DOM ID to React component instance. */
// The React component instance is mapped based on ReactRootID
var instanceByReactRootID = {};

/** Mapping from reactRoot DOM ID to `container` nodes. */
// Container Mapping based on ReactRootID
var containersByReactRootID = {};

/** * @param {DOMElement} container DOM element that may contain a React component. * @return {? string} A "reactRoot" ID, if a React component is rendered. */
function getReactRootID(container) {
  return container.firstChild && container.firstChild.id;
}

var ReactMount = {
  renderComponent: function(nextComponent, container) {
    // The above logic contains component updates and event registration
    // Get/generate reactRootID
    var reactRootID = ReactMount.registerContainer(container);
    // Map the React component
    instanceByReactRootID[reactRootID] = nextComponent;
    // Call the component's own method
    nextComponent.mountComponentIntoNode(reactRootID, container);
    return nextComponent;
  },
  registerContainer: function(container) {
    / / get reactRootID
    var reactRootID = getReactRootID(container);
    if (reactRootID) {
      // If yes, check whether the ID is "reactRoot" ID; otherwise, return null
      // If one exists, make sure it is a valid "reactRoot" ID.
      reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);
    }
    if(! reactRootID) {// No valid "reactRoot" ID found, create one.
      // If the ID does not exist, a new ID is returned
      reactRootID = ReactInstanceHandles.getReactRootID(
        globalMountPointCounter++
      );
    }
    / / map container
    containersByReactRootID[reactRootID] = container;
    returnreactRootID; }},Copy the code
// core/ReactInstanceHandles.js
var ReactInstanceHandles = {
  getReactRootID: function(mountPointCount) {
    return '.reactRoot[' + mountPointCount + '] ';
  },
  getReactRootIDFromNodeID: function(id) {
    var regexResult = /\.reactRoot\[[^\]]+\]/.exec(id);
    return regexResult && regexResult[0]; }},Copy the code

Mount the component to the DOM node method

// core/ReactComponent.js
var ReactComponent = {
  Mixin: {
    mountComponent: function(rootID, transaction) {
      // The component lifecycle is related to ref

      this._rootNodeID = rootID;
    },
    mountComponentIntoNode: function(rootID, container) {
      // React transaction is involved
      var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
      // For this discussion, you can simply understand it as:
      // this._mountComponentIntoNode.call(this, rootID, container, transaction)
      transaction.perform(
        this._mountComponentIntoNode,
        this,
        rootID,
        container,
        transaction
      );
      ReactComponent.ReactReconcileTransaction.release(transaction);
    },
    _mountComponentIntoNode: function(rootID, container, transaction) {
      // This includes some time calculation, no interpretation
      var renderStart = Date.now();
      // Markup is the HTML markup returned
      / / this. MountComponent is ReactNativeComponent. Mixins. MountComponent
      var markup = this.mountComponent(rootID, transaction);
      ReactMount.totalInstantiationTime += (Date.now() - renderStart);

      var injectionStart = Date.now();
      // Asynchronously inject markup by ensuring that the container is not in
      // the document when settings its `innerHTML`.
      The following code is used to determine how the markup needs to be inserted into the value DOM node.
      var parent = container.parentNode;
      if (parent) {
        var next = container.nextSibling;
        parent.removeChild(container);
        container.innerHTML = markup;
        if (next) {
          parent.insertBefore(container, next);
        } else{ parent.appendChild(container); }}else {
        container.innerHTML = markup;
      }
      ReactMount.totalInjectionTime += (Date.now() - injectionStart); }}},Copy the code

Generate Markup Markup

// core/ReactNativeComponent.js
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true.'number': true};

ReactNativeComponent.Mixin = {
  mountComponent: function(rootID, transaction) {
    ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
    // Parameter verification is not interpreted
    assertValidProps(this.props);
    // The HTML tag is returned
    return (
      this._createOpenTagMarkup() +
      this._createContentMarkup(transaction) +
      this._tagClose
    );
  },
  _createOpenTagMarkup: function() {
    var ret = this._tagOpen;
    // Unread (event registration/CSS styles/DOM attributes)

    return ret + ' id="' + this._rootNodeID + '" >';
  },
  _createContentMarkup: function(transaction) {
    // Ignore dangerouslySetInnerHTML
    var contentToUse = this.props.content ! =null ? this.props.content :
      CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
    varchildrenToUse = contentToUse ! =null ? null : this.props.children;
    if(contentToUse ! =null) {
      // Content == null and children is string/number
      Zong is learning the source code of React.
      return escapeTextForBrowser(contentToUse);
    } else if(childrenToUse ! =null) {
      // Multiple children
      return this.mountMultiChild(
        flattenChildren(childrenToUse),
        transaction
      );
    }
    return ' '; }},Copy the code
// utils/escapeTextForBrowser.js
var ESCAPE_LOOKUP = {
  "&": "&amp;".">": "&gt;"."<": "&lt;"."\" ": "&quot;"."'": "&#x27;"."/": "&#x2f;"
};

function escaper(match) {
  return ESCAPE_LOOKUP[match];
}

var escapeTextForBrowser = function (text) {
  var type = typeof text;
  var invalid = type === 'object';
  if (text === ' ' || invalid) {
    return ' ';
  } else {
    if (type === 'string') {
      return text.replace(/[&><"'\/]/g, escaper);
    } else {
      return (' '+text).replace(/[&><"'\/]/g, escaper); }}};Copy the code

So that’s it, implement HTML element rendering.