This is the 8th day of my participation in Gwen Challenge.More article challenges

TinyReact project: https://github.com/zelixag/tiny-react-learn/tree/master/tiny-react

Today we will learn how to create a Virtual DOM object, and how to convert a virtualDOM object into a realDOM object

Create a VirtualDOM object

VirtualDOM is converted from JSX. JSX is converted to a call to the react. createElement method, which passes in the element’s type, attributes, and child elements. The createElement method returns the constructed Virtual DOM object. So let’s go back and implement the createElement method.

As we said at the beginning, we want to implement tiny-React ourselves, so JSX should not convert it to react. createElement. Instead, Babel will convert to Tinyreact.createElement when JSX is compiled. How do we tell Babel about this?

  1. The first solution: put a comment at the top of every page that contains JSX

/** @jsx TinyReact. CreateElement */ Then Babel will compile the React object and replace it with the TinyReact object we want. But it’s feasible but it’s too cumbersome to operate.

  1. The second option is to add one to the root of our tiny-React project.babelrcThe react file specifies that when react is compiled, use TinyReact. CreateElement so that we can configure it in one place. All JSX searches are converted this way
// .babelrc
{
  "presets": [
    "@babel/preset-env"["babel/preset-react",
      {
        "pragma": "TinyReact.createElement"}}]]Copy the code

CreateElement = TinyReact createElement = TinyReact. CreateElement = TinyReact. CreateElement = TinyReact.

How to implement createElement

The createElement method returns a virtualDOM object based on the parameters passed in. The object has the type attribute for the node type, the props attribute for the node, and the children attribute for the children node. We just need to build the virtualDOM object with the parameters passed in.

Create it in the TinyReact foldercreateElement.jsFile, let’s create the createElement method

// createElement.js
export default function createElement (type, props, ... children) {
  return {
    type,
    props,
    children
  }
}
/ / TinyReact/index. Js file

import createElement from "./createElement"

export default {
  createElement
}
Copy the code

Compile JSX using tinyreact.createElement, which we wrote

// src/index.js
import TinyReact from "./TinyReact"

const virtualDOM = (
  <div className="container">
    <h1>Hello Tiny React</h1>
    <h2 data-test="test">(Code killer)</h2>
    <div>Nested 1<div>Nested 1.1</div>
    </div>
    <h3>(Observation: This will change)</h3>{= = 1 & 2<div>Render current content if 2 and 1 are equal</div>}
    {2 == 2 && <div>2</div>}
    <span>This is a paragraph</span>
    <button onClick={()= >Alert (" Hello ")}> Click on me</button>
    <h3>This will be deleted</h3>
    2, 3
    <input type="text" value="13" />
  </div>
)

console.log(virtualDOM)
Copy the code

The browser displays the converted virtualDOM as:

As you can see, there are props, Type, and children properties in this object. So far we have created a basic virtualDOM object.

To see what we did, we prepared a piece of JSX code ahead of time that would convert every element in JSX to a tiny.createElement method, which would execute our own createElement method and output the virtualDOM object.

So far we have some problems with virtualDOM:

  1. Problem 1: Related to the text node, we can see that our virtualDOM text node exists in the form of text string in virtualDOM, which obviously does not meet our requirements. Our requirement is that even text nodes be represented as node objects.

Correct expression:

{type: 'text', props: {textContent: 'Hello'}}

So let’s go ahead and optimize the createElement method, and we’re going to iterate over the children property, and we’re going to say if this child node is an object and we’re not going to do anything about it, if it’s not an object we’re going to say it’s a text node, it’s a text node and we’re going to manually call createElement to turn that text into a node object.

export default function createElement (type, props, ... children) {
  constchildElement = [].concat(... children).map(child= > {
  // The object is returned directly, either as a text node
    if(child instanceof Object) {
      return child
    } else {
    // Call createElement manually
      return createElement("text", {textContent: child}); }})return {
    type,
    props,
    children: childElement
  }
}
Copy the code

So we’ve successfully solved the first problem by turning the text node into a text node object

2. Question 2: in JSX, there is js expression and its value istrueorfalseAs we learned earlier, virtualDOM does not displaytrue, flase ,nullThese three values, we should clear these three values

To the virtualDOM object

The implementation code is as follows:

export default function createElement (type, props, ... children) {
  // Instead of using map, we can use reduce
  constchildElement = [].concat(... children).reduce((result, child) = > {
  // Filter out true child null
    if(child ! = =true&& child ! = =false&& child ! = =null)  {
      if(child instanceof Object) {
        result.push(child)
      } else {
        result.push(createElement("text", {textContent: child})); }}return result;
  }, [])
  return {
    type,
    props,
    children: childElement
  }
}

Copy the code

The true,false, and null nodes do not exist

  1. Question 3: When we use the React component, we can use the props. Children property to get the child node of the component. However, in virtualDOM, there are no children in the props. So we also need to put props into props. How do you put it? Easy

Assign ({children: childrenElements}, props) to add the children attribute


export default function createElement (type, props, ... children) {
  constchildElements = [].concat(... children).reduce((result, child) = >{...return {
    type,
    props: Object.assign({children: childrenElements}, props)// Add the children attribute,
    children: childElements
  }
}
Copy the code

Convert a normal virtualDOM object to a real DOM object

To convert a normal virtualDOM object into a real DOM object, we need to implement a method called the Render method. We can do some preparatory work with createElement.

  1. Create it in the folder TinyReactrender.jsRender file, create the method with the same name, and expose it in the file index in the same directory
import createElement from "./createElement"
import render from "./render"

export default {
  createElement,
  render
Copy the code
  1. insrc/index.jsThe render function needs a real DOM mount point insrc/index.htmlAdd tags<div id="root"></div>

import TinyReact from "./TinyReact"

const root = document.getElementById("root") // Get the mounted node

const virtualDOM = (
  <div className="container">...</div>
)
TinyReact.render(virtualDOM, root) // Call the render method
console.log(virtualDOM)
Copy the code

Good!! Now that the preparation is complete, let’s implement the Render method, which takes two arguments: the first is virtualDOM, the second container, and the third oldDOM. OldDOM is used in diff and is temporarily unavailable when first mounted. We can declare diff files and create diff methods. If you want to convert a virtualDOm object without an oldDOM object, there’s one thing you need to pay attention to here: Is this a normal virtualDOm object or a component virtualDOm object? What does that mean? This means that we can either be JSX directly as shown in the figure, or a component JSX of function type or object type.

We need to call another method called mountElement to handle the situation. Accepts two parameters: virtualDOM, container, For the time being we will only deal with normal virtualDOM objects. Create a mountnativeelement. js function that declares the same name mountNativeElement to handle the conversion of normal virtualDOM objects into real DOM objects.

So how do we convert here? – First we check what the node type is, is it an element node or a text node, if it is an element node we should create an element of that type, if it is a text node we will create text.

// mountNativeElement.js
export default function mountNativeElement (virtualDOM, container)  {
  let newElement = null
  if(virtualDOM.type==="text") {// Text node
    newElement = document.createTextNode(virtualDOM.props.textContent);
  } else {
    // Element node
    newElement = document.createElement(virtualDOM.type)
  }
}
Copy the code

VirtualDOM’s children are all in virtualDOM’s children. The next step is to recurse through the children to create the child node element

import mountElement from "./mountElement"
export default function mountNativeElement (virtualDOM, container)  {
  let newElement = null...// Create child nodes recursively
  virtualDOM.children.forEach((child) = > {
    mountElement(child, newElement);
  })

  container.appendChild(newElement);
}
Copy the code

This completes a preliminary VirtualDOM conversion:

New files and methods are added this time

To create a virtualDOM object, pass the createElement parameter to create a basic virtualDOM object. Remember to convert the text node to a virtualDOM object format {type: ‘text’, props: {textContent: ‘Hello’}}, according to the first day we will use the react time notice we should JSX contains false, true, null excluding off, and we have a props in the react children attribute we also want to add, create good after a virtualDOM

as mount point in SRC /index.html. Then create render method in TinyReact/render. The render method is for framework users. We call the render function in SRC /index.js. The render method takes three arguments: virtualDOM,container, and oldDOM for diff. Render inside, still need to call other methods invoked the diff method, receiving virtualDOM three parameters, container, oldDOM display table. If oldDOM does not exist, then we call mountElement separately. This method handles both cases: component virtualDOM or plain virtualDOM. In mountElement we call the mountNativeElement method, which is used to handle normal virtualDOM, and creates element nodes and text nodes in the real DOM. The children of the node create the real DOM recursively and then add it to the container.

On day 3 we will learn how to add attributes to DOM objects and render components. Stay tuned!!