introduce

React uses a declarative componentized UI to make your code more reliable and easy to debug.

React code is very complex, we will simplify the implementation of the React structure core API:

  • React.createElement
  • React.Component
  • ReactDom.render

Let’s start with JSX

1. What is JSX?

  • ReactUse JSX instead of regularJavaScript.
  • JSX is one that looks a lot like XMLJavaScriptSyntax extension.

2. Why use JSX in React?

  • JSX executes faster because it compiles toJavaScriptThe code has been optimized.
  • Type safety checks prevent injection attacks, and errors can be found during compilation.
  • Writing modules is easier and faster.

3. How to use JSX, I will not elaborate here, suggest to read the official document.

Babel-loader precompiles JSX to React. CreateElement (…)

Next, I will implement the simple React core API step by step. I will analyze the three types of native DOM, function component, and class component.

Take a look at the sample test source index.js:

As you know, JSX is converted to a normal JavaScipt object. Here I declare a JSX variable that contains elements. In addition, I add a custom component. Look down with this question. Instead, import the familiar React-DOM package that provides the Render method, which most commonly takes two arguments, the JSX I want to render and the root container to hang from.

import ReactDOM from 'react-dom'; 

// function Component
function Comp(props){
  return <h2>hi {props.name}</h2>
}

const jsx  = (
  <div id="demo" style={{color:"red",border:'1px solid blue'}} >
    <span>hi</span>
    <Comp name="Function component"></Comp>
  </div>
);
console.log(jsx); // Output the structure
ReactDOM.render(jsx,document.querySelector('#root'))
Copy the code

> < span style = “max-width: 100%; clear: both;The error says there is no declaration at allReactBecause I didn’t import itReact. Why importReact? The reason is that JSX is inwebpackI use it when I’m packingbabel-loaderConverted toReact.createElementIn the form. The diagram below:

// function Component
function Comp(props) {
  return React.createElement(
    "h2".null."hi ",
    props.name
  );
}

const jsx = React.createElement(
  "div",
  { id: "demo".style: { color: "red".border: '1px solid blue' } },
  React.createElement(
    "span".null."hi"
  ),
  React.createElement(Comp, { name: "Function component"}));Copy the code

Let’s see what the JSX output looks like:

In terms of data structure, it is a common Object. There are many properties worth paying attention to, such as:

  • keyIt is like a unique value passed when rendering a list. The main function is to improve the value indiffProcess efficiency;
  • propsAttribute contains both the current element attribute and child elements (children), observe carefullychildrenThe element data structure is the same as its parent;
  • refUsed for referenceDOM;
  • typeIt is even more useful to specify the type of the current tag;

As can be seen from the above, VDOM is actually used to describe the structure of our DOM JavaScript objects, why we need this virtual DOM will be explained later.

practice

CreateElement (JSX) {React. CreateElement (JSX) {React. CreateElement (JSX) {React.

import React from 'react.js'; // React import ReactDOM from 'react-dom'; // function Component function Comp(props){ return <h2>hi {props.name}</h2> } const jsx = ( <div id="demo" Style = {{color: "red", border: '1 px solid blue'}} > < / span > < span > hi < Comp name = "functional components" > < / Comp > < / div >). console.log(jsx); / / output see concrete structure under ReactDOM. Render (JSX, document. QuerySelector (' # root '))Copy the code

If you don’t know the createElement pass-spec parameters, we can write new pictures to read

Judging by the output parameters, there must be onetypeParameter 2 represents the attribute of the element. Parameter 3 represents several child elements. In addition, we know from the above JSX output resultspropsThere’s a property under thatchildrenProperties,childrenIt’s not independent, it’s collecting in an array and putting it in a separate arraypropsIn the water.

Add vType attributes through label type handling to distinguish component types when vDOM is converted to DOM

The react.js code looks like this:

/**
 *
 * createElement
 * @param {any} Type Label type or component type, such as div *@param {Element Attribute} Props Tag attribute *@param {something child Element} Children A number of children */ of varying numbers
function createElement(type, props, ... children) {
  console.log('createElement'.arguments);
  props.children = children;
  return { type, props};
}
export default { createElement };
Copy the code

Run the following error:Why the error, because of itselfReactIt is very robust and will be checked when receiving the parameter. We support return here{ type, props }Two parameters,ReactAs we saw earlierref,keyAnd so on; So we can only create one herereact-dom.js, with their own creationrenderMethod to achieve rendering.react-dom.jsThe code is as follows:

/** * render render function ** @param {Object} vnode is created by createElement * @param {Element} container mount container */ function render(vnode, container) { container.innerHTML = `<pre>${JSON.stringify(vnode, null, 2)}</pre>`; } export default { render };Copy the code

Page output resultAnd then think about, is there any way that we canrenderThe receivedvnodeInto the real thingDOMNodeThere’s a lot of logic here, so I’m going to create a new onevdom.jsFile to process.

The code for vdom.js is as follows:

 /** * createVNode creates a virtual node and processes the vDOM returned by createElement *@export
 * @param {Number} Vtype The type of the element, 1: native element, 2: function component, 3: class component *@param {Object} Type Label element type *@param {Object} Props tag attribute */
export function createVNode(vtype, type, props) {
  const vnode = { vtype, type, props };
  // console.log('vnode', vnode);
  return vnode;
}
Copy the code

Here we have an extra vtype. Why do we do that? I this considering the native HTML native, in addition to consider a custom function components and a Class type components, judged by vtype parameters, here to see new react upstairs. Return and no vtype in js, so through the type special handling is needed here:

function createElement(type, props, ... children) {
  props.children = children;
  delete props.__source; // Remove useless attributes
  delete props.__self;
  // type: label type, such as div
  // vtype: indicates the component type
  let vtype;
  if (typeof type === 'string') {
    // Native tags
    vtype = 1;
  } else if (typeof type === 'function') {
    if (type.isClassComponent) {
      / / class components
      vtype = 2;
    } else {
      // Function components
      vtype = 3; }}return createVNode(vtype, type, props); // vdom.js 
}

// Used to implement the class component
export class Component {
  // It is used to distinguish between classes and functions, because Typeof returns funciton for both classes and functions
  static isClassComponent = true;
  constructor(props) {
    this.props = props;
    this.state = {};
  }
  // You can use setState freely
  setState(){}}Copy the code

The next important thing to do is to convert the VDOM to the real DOM. We add the initVNode method to vdom.js and export it. Inside the function, text nodes, element tags, function components and class components are processed respectively. Properties and special properties are handled. The code is as follows:

/** * VDOM is converted to DOM * initialize the virtual node *@export
 * @param {Object} vnode* /
export function initVNode(vnode) {
  const { vtype } = vnode;
  if(! vtype) {// Text node
    return document.createTextNode(vnode);
  }
  if (vtype === 1) {
    // Native tags
    return createElement(vnode);
  } else if (vtype === 2) {
    / / class components
    return createClassComponent(vnode);
  } else if (vtype === 3) {
    // Function components
    returncreateFunComponent(vnode); }}/** * Create native element tag * function component and Class component creation are eventually executed to the native.... *@param {Object} vnode
 * @returns* /
function createElement(vnode) {
  // Create an element based on type
  const { type, props } = vnode;
  const node = document.createElement(type);

  // Handle attributes, native custom attributes, special attributes children
  const{ key, children, ... rest } = props;Object.keys(rest).forEach((k) = > {
    // Handle special attribute names in JSX: className, htmlFor
    if (k === 'className') {
      node.setAttribute('class', rest[k]);
    } else if (k === 'htmlFor') {
      node.setAttribute('for', rest[k]);
    } else if (k === 'style' && typeof rest[k] === 'object') {
      < span style = "max-width: 100%; clear: both; min-height: 1em
      const style = Object.keys(rest[k])
        .map((s) = > `${s}:${rest[k][s]}`)
        .join('; ');
      node.setAttribute('style', style);
    } else{ node.setAttribute(k, rest[k]); }});// Recursive child,// children parent => node
  children.forEach((c) = > {
    // console.log('children',c)
      node.appendChild(initVNode(c));
  });
  return node;
}

/** * Create Class component **@param {Object} vnode
 * @returns* /
function createClassComponent(vnode) {
  // According to the class component, type is the class component declaration
  const { type, props } = vnode;
  const component = new type(props);
  const vdom = component.render();
  return initVNode(vdom);
}
/** * Create the function component **@param {Object} vnode
 * @returns* /
function createFunComponent(vnode) {
  Type is a function
  const { type, props } = vnode;
  const vdom = type(props);
  return initVNode(vdom);
}

Copy the code

For example, JSX class and for are reserved words, so use className, htmlFor, etc. Also tested the simple handling of style inline style.

JSX about properties props:

  • classThe property needs to be written asclassName.forThe property needs to be written ashtmlForThis is becauseclassandforisJavaScriptReserved word of.
  • Use directly on the labelstyleProperty is written asstyle={{}}The outer curly braces tell JSX that this is JS syntax and that, unlike real DOM, the property value cannot be a string but must be an object. Note that property names also require camel name. namelymargin-topTo be writtenmarginTop.
  • this.propsDon’t use thechildrenAs the property name of the object. becausethis.props.childrenGets all the child tags under the tag.this.props.childrenThere are three possibilities for the value of
    • It is if the current component has no child nodesundefined;
    • If there is a child node, the data type isobject;
    • If there are multiple child nodes, the data type isarray.

So be careful with this.props. Children. The official recommendation is to use React.Children. Map to iterate over child nodes without worrying about datatype errors.

// class comp class Comp2 extends Component { render() { return ( <div> <h2>hi {this.props.name}</h2> </div> ); }} const Users = [{name: 'Hank ', age: 30}, {name: 'nimo', age: 7},]; // vdom const jsx = ( <div id="demo" style={{ color: 'red', border: '1 px solid blue'}} > < / span > < span > hi < Comp name = "functional components" > < / Comp > < Comp2 name = "class components" > < / Comp2 > < ul > {users. The map ((user) = > ( <li key={user.name}>{user.name}</li> ))} </ul> </div> );Copy the code

Running results:

Why is Li not displayed normally? In this case, createElement handles children only for a single virtual DOM, not for an array of values. And here’s the treatment

ForEach (c => {console.log('children',c)) // If the child is an array, If (array.isarray (c)) {c.map(el => {node.appendChild(initVNode(el))})} else{{node.appendChild(initVNode(el))} node.appendChild(initVNode(c)) } })Copy the code

Test result OK

React is a simple implementation that will help you learn React. If you have any questions or suggestions, please leave a comment at….

Example source code address

Parametric literature

React API