preface

Today we will not share React specific grammar, components, communication, Ref, Portals, Context, Hoc, Hook and other knowledge points, these things, you can be familiar with the development task by carefully looking at the official documents, but today I want to talk about React working principle.

If you want to know the basic knowledge of React, please check my demo, nearly 100 Start React Antd Shop

Lets Go !

What is the virtual Dom? What is Jsx? How does React work? . .

Let's conduct a shallow analysis of React according to the above three questions!

What is the virtual Dom?

Virtual DOM (Virtual DOM) is a programming concept in which the UI is stored in memory as a Virtual structure and then converted into a real DOM through compilation. So what is it?

The object, yes, is a Javascript object that represents the Dom information and structure. When the state changes, the object structure is rerendered. This Javascript object is called a Virtual Dom.

Why not just manipulate the DOM? Simply because THE DOM is slow to operate, even small changes can cause pages to be rearranged and redrawn, which costs performance. Therefore, the DIff algorithm can be used to operate Js objects in batches and minimize Dom operations, so as to improve performance. Diff we post analysis.

What is Jsx?

Jsx is a syntax extension for javascript + XML, but any {} will be executed as javascript, otherwise it will be XML.

Why do YOU need Jsx? Maybe it’s because Jsx templates are concise and syntax is flexible, which I don’t know.

How babel-Loader precompiles Jsx to React. CreateElement (…) Function execution.

So how does React work?

Start with Jsx, first let’s look at a piece of code on the official website

A regular React component

class HelloMessage extends React.Component {
  render() {
    return (
      <div className="HelloClass" num="1">
        Hello {this.props.name}
        <span> is span</span>
      </div>
    );
  }
}
ReactDOM.render(
  <HelloMessage name="Taylor" />.document.getElementById('hello-example'));Copy the code

This regular React component is babel-Loader.

class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      "div"./ / type
      { className: "HelloClass".num: "1" }, / / property
      "Hello "./ / content
      this.props.name, // props
      React.createElement( // Nesting - same thing
        "span".null." is span")); }}// Class components are also converted to 'React. CreateElement (...) 'function execution

ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }), 
document.getElementById('hello-example'));
Copy the code

After a simple analysis of the code, did you find that the principle of Jsx we mentioned above is verified? After babel-Loader compilation, it becomes React.createElement(…). Function, if nested, then React. CreateElement (…) Also the nested

Class HelloMessage extends React.Com props class HelloMessage extends React.Com props Class HelloMessage extends React.Com props

okey ! At this point, we can have a simple conclusion, which is:

  1. The React component inherits from the React.ponent classclass HelloMessage extends React.Component
  2. Jsx syntax compiled by Babel-Loader becomesReact.createElement(...) Function performs, and layers are nested
  3. The element is mounted on the page through the reactdom.render method

Let’s analyze it in turn and simply implement it:

React.component

export default function Component (props) {
  // Contains the props parameter
  this.props = props;
}
// Define an identity for a class component and a function component. The source code is identified as such, although for some reason it is not written as a Boolean
Component.prototype.isReactComponent = {};
Copy the code

React.createElement(…)

Back to the React. CreateElement method (…). , then the React. CreateElement method (…). How many parameters do you receive? From the above example, it can be concluded:

  • Parameter 1: type (element type rendered, component type, etc.)
  • Parameter 2: attributes (attributes of an element or component, including className, ID, custom props, etc.)
  • Parameter 3: content of the child nodeThere may be many children

Okey, with all that said, let’s hand write a mini React. CreateElement ().

When writing the React. CreateElement method (…). What parameter is passed in, what value is returned, and what parameter is passed in. What parameter is passed in

So what does that return? Let’s look at the createElement source code:

/ / position: the react/packages/react/SRC/ReactElement js 348 rows
// Delete the previous processing, easy to read
export function createElement(type, config, children) {
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
Copy the code

We need to return a ReactElement object containing type, key, ref, self, source, props, etc. We want to know how react works. Let’s drop key, ref, self for a moment, and let’s go ahead and do the code

/* Return props type (props type); /* return props type (props type); "Border "," name ":" function component ", __source: {... }, __self: undefined} children: undefined */
function createElement (type, config, ... children) {
  // remove __source: {... }, __self: undefined property, easy console view
  if (config) {
    delete config.__source;
    delete config.__self;
  }
  // Key.ref.slef is not considered here
  constprops = { ... config,/* createTextNode specifies the data structure of the text node. If the child node is an object, the child node has a child node. {type:" type ", props:{}}. Babel -Loader compiles JSX to React. CreteElement nested */  
    children: children.map(child= >
      typeof child === "object" ? child : createTextNode(child)
    )
  };
  console.log({type,props});
  return {
    type,
    props
  };
}

// Select "TEXT" from "TEXT"; // Select "TEXT" from "TEXT";
function createTextNode (text) {
  return {
    type: "TEXT".props: {
      children: [].nodeValue: text
    }
  };
}
Copy the code

Let’s take a look at the final return, my own sample

okey ! A mini version of the React. CreateElement () method has been implemented. Let’s go to the render method

ReactDOM. Render (…)

The render method in the react-dom module seems to pass two parameters, but there are actually three parameters, and the third one is not used very often. Container to place after parsing 3. Callback function

/ / position: the react/packages/react - dom/SRC/client/ReactDOMLegacy js 287 rows
export function render(
  element: React$Element<any>, / / the react elements
  container: Container, // The container to be placed
  callback: ?Function.// The callback function
) {
  / /... A lot of logic
  / / return calls legacyRenderSubtreeIntoContainer methods here
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}
Copy the code

LegacyRenderSubtreeIntoContainer ultimately go down

/ / position: the react/packages/react - dom/SRC/client/ReactDOMLegacy js 175 rows
function legacyRenderSubtreeIntoContainer(parentComponent: ? React$Component<any, any>, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function.) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  // Determine if the component is rendered for the first time. If so, create it and update the DOM, otherwise diff the DOM directly
  if(! root) { root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot;if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    / / update the dom
    unbatchedUpdates(() = > {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    / / update the dom
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}
Copy the code

There is still a simple idea in mind, and that is

  • 1,renderThe react method takes three arguments, the react element, the container to place after parsing, and the callback function
  • 2, follow the calllegacyRenderSubtreeIntoContainerMethods, create the dom | | to update the dom, edge involves two key here, tooReactCore knowledge pointsFiber data structure, DIFFF algorithmWe’ll find out later

Let’s implement the render method in a simple way:

Create a render function that does two things (aside from the callback function)

/* Vnode JSX {vnode -> node} vnode JSX {vnode -> node} container.appendChild(node); To mount the real DOM to container */
function render (vnode, container) {
  const node = createNode(vnode, container);
  node && container.appendChild(node);
}
Copy the code

Implement a createNode to convert the virtual DOM to the real DOM

/* the virtual dom is rendered as the real dom. Vnode the parentNode of the virtual dom parentNode, that is, the corresponding parent container to which the virtual dom is mounted */
function createNode (vnode, parentNode) {
  // Transform the final real DOM node of the combination
  let node = null; 
  /* props: props and children */
  const { type, props } = vnode;

  // Todo generates DOM nodes based on the node type
  if (type === "TEXT") {
    // TEXT node, type = "TEXT", nodeValue = content value of the TEXT node
    node = document.createTextNode("");
  } else if (typeof type === "string") {
    // Create a native tag node with a string type such as div, p, span, etc
    node = document.createElement(type);
  } else if (typeof type === "function") {
    // Class component and function component, type is function
    // isReactComponent custom identifier, defined on the Component prototype
    node = type.prototype.isReactComponent
      ? updateClassComponent(vnode, parentNode) // The class component converts dom methods
      : updateFunctionComponent(vnode, parentNode);  // The function component converts the DOM method
  }

  /* render (props. Children); /* render (props. Children); Child nodes are like class components, function components, and we put them outside */
  reconcileChildren(node, props.children);

  /* Update dom node: props: props and child node properties and child node content values   
  updateNode(node, props);

  return node;
}
Copy the code

Now we have a simple render method logic in front of us, all that remains is to implement the corresponding transformation logic step by step, let’s do one by one:

Class components

Let’s mock a class component that inherits from our custom react.ponent class.

class ClassComponent extends Component {
  render () {
    return (
      <div className="border">
        ClassComponent - {this.props.name}
      </div>); }}Copy the code

Parse a class component by instantiating it and then executing Render to return the corresponding virtual DOM and then converting it through the createNode logic above

function updateClassComponent (vnode, parentNode) {
  // Get the virtual DOM type and properties
  const { type, props } = vnode;
  // instantiate, because the class component type is a function, pass the attribute props to it
  const instance = new type(props);
  // Execute the render function of the class component to get the virtual DOM
  const vvnode = instance.render();
  // Convert the virtual DOM to the real DOM according to the conversion logic
  const node = createNode(vvnode, parentNode);
  // Return the real DOM
  return node;
}
Copy the code

Function component

function FunctionComponent (props) {
  return <div className="border">FunctionComponent-{props.name}</div>;
}
Copy the code

A function component is simpler than a class component, executing the function directly, returning the corresponding virtual DOM and translating it through the createNode logic

function updateFunctionComponent (vnode, parentNode) {
  const { type, props } = vnode;
  const vvnode = type(props); 
  const node = createNode(vvnode, parentNode);
  return node;
}
Copy the code

Child nodes

Iterate over the child nodes, recursively executing render to go to the createNode conversion logic;

function reconcileChildren (node, children) {
  for (let i = 0; i < children.length; i++) {
    constchild = children[i]; render(child, node); }}Copy the code

Update the dom

To update the DOM node, nextVal => props and child node properties and their values are then mounted to the real DOM node

function updateNode (node, nextVal) {
  Object.keys(nextVal)
    .filter(k= >k ! = ="children")
    .forEach(k= > {
      // console.log(node[k] + "----" + nextVal[k]);
      node[k] = nextVal[k];
    });
}
Copy the code

Down ~

Going back to our official example, isn’t a simple principle already clear

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}
ReactDOM.render(
  <HelloMessage name="Taylor" />.document.getElementById('hello-example'));Copy the code

The mini – react – demo entrance

Follow-up: Let’s study fiber data structure and DIFF algorithm again.

Welcome to like, a little encouragement, a lot of growth