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

We’ve already converted virtualDOM to real DOM, but these real DOM don’t have any attributes, so let’s add attributes to the real DOM

Add attributes to the real DOM

Analysis: These properties are stored in the props of the virtualDOM. When creating an element, we need to find the props of the virtualDOM and traverse the props property. Then we can add these properties to the real DOM element. But when we add attributes, we need to consider a few different cases, because we have to do different things in different cases, if it’s an event attribute, then we need to add events to that element; We also need to check whether this attribute is a value attribute or a Checked attribute, because those two attributes cannot be set using setAttribute. We also need to check whether this attribute is a children attribute. Children is not an attribute, it is a child element, but it also exists in props; We also need to see if it’s a className attribute, and if it’s a className we add a class attribute to the element; If it is a common attribute, you can use the setAttribute method to set the real DOM.

Code implementation:

export default function updateNodeElement(newElement, virtualDOM) {
  // Get the property object corresponding to the node
  const newProps = virtualDOM.props

  Object.keys(newProps).forEach(propName= > {
    // Get the attribute value
    const newPropsValue = newProps[propName]

    OnClick => click

    if(propName.slice(0.2) = = ="on") {
      const eventName = propName.toLowerCase().slice(2)
      // Add events to the element
      newElement.addEventListener(eventName, newPropsValue)
    } else if(propName === "value" || propName === "checked") {
      newElement[propName] = newPropsValue
    } else if(propName ! = ="children") {
      if(propName === "className") {
        newElement.setAttribute('class', newPropsValue)
      } else {
        newElement.setAttribute(propName, newPropsValue)
      }
    }
  })
}

Copy the code

Reference this method when generating a real DOM folder:

import updateNodeElement from "./updateNodeElement"

export default function mountNativeElement (virtualDOM, container)  {
  let newElement = null...else {
    // Element node
    newElement = document.createElement(virtualDOM.type);
    updateNodeElement(newElement, virtualDOM);

Copy the code

Summary: When we create the real element node, we call updateNodeElement to add attributes to the element. The attributes are in the virtualDOM props, so the updateNodeElement passes two parameters, One is the current real DOM,newElement(for whom attributes are set), and one is virtualDOM (where those attributes are hidden), and then pull out all the properties in the props, and add the listener for the event using the addEventListener, Value and Checked use newElement[propName] = newPropsValue, then all other objects except children are converted to class for className, and all other objects are directly setAttribute.

Component rendering differentiates function components from class components

What the component looks like before it is converted to virtualDOM because we call the mountElement method whenever we render a component or a DOM element. In this method we want to distinguish the component from the normal virtualDOM element, because the normal virtualDOM element is handled differently from the component

// The original component
const Heart = () = > <span>&hearts;</span>
Copy the code

Calling component mode

<Heart />
Copy the code

Below is the VirtualDOM of the component

type: f function() {},
props: {}
children: []
Copy the code

Analysis: We found that the component type is a function, while normal VirtualDOM is a string. So call mountElement to separate processing, The component execution method is mountComponent(virtualDOM, container). The common virtualDOM execution method is mountNativeElement(virtualDOM, Container). VirtualDOM is a component if the virtualDOM type attribute is a function. Code:

import isFunction from './isFunction';
import mountComponent from './mountComponent';
import mountNativeElement from './mountNativeElement';
export default function mountElement(virtualDOM, container) {
  // Component VS NativeElement
  if(isFunction(virtualDOM)) {
    MountComponent handles the component method
    mountComponent()
  } else {
    // Handle the native virtualDOM method mountNativeElementmountNativeElement(virtualDOM, container); }}// isFunction.js
VirtualDOM type is a function
export default function isFunction(virtualDOM) {
  return virtualDOM && typeof virtualDOM.type === "function"
}
Copy the code

Above we distinguish component virtualDOM and common virtualDOM, we have written common virtualDOM processing method in front, now write component virtualDOM processing method, components are divided into two types of components and function components, although the essence is a function component, but still separate processing more clear. Isfuntioncomponent.js creates a function of the same name to determine whether the component Virtual is a function or a class. After that, the function component calls the function component handler and the class component calls the class component handler

import isFunctionComponent from "./isFunctionComponent";

export default function mountComponent(virtualDOM, container) {
  // Determine whether the component is a class component or a function component
  if(isFunctionComponent(virtualDOM)) {
    console.log('Function components... ')}else {
    console.log('Class component')}}// isFunctionComponent.js
import isFunction from "./isFunction"

export default function isFunctionComponent(virtualDOM) {
  const type = virtualDOM.type
  return(type && isFunction(virtualDOM) && ! (type.prototype && type.prototype.render)) }Copy the code

Function component goes function component handler buildFunctionComponent, The class component handles the buildClassComponent method. You can get the corresponding virtualDOM by calling virtualdom.type () in the buildFunctionComponent method, because the function component type is a method.

import isFunctionComponent from "./isFunctionComponent";
import mountNativeElement from "./mountNativeElement";

export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null
  // Determine whether the component is a class component or a function component
  if(isFunctionComponent(virtualDOM)) {
    nextVirtualDOM = buildFunctionComponent(virtualDOM);
    console.log("nextVirtualDOM", nextVirtualDOM);
  }
  mountNativeElement(nextVirtualDOM, container)
}

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type()
}

// src/index.js
function Heart() {
  return <div>&hearts;</div>
}
TinyReact.render(<Heart />, root)
Copy the code

The results are:

The function component has been rendered successfully, but we still have a problem that is not solved. If the function component is not called by the original tag, we can not call it directlymountNativeElementIf it is a component, call the mountComponent method recursively, until isFunction and type are not functions or VirtualDOM

// mountComponent.js
export default function mountComponent(virtualDOM, container) {...if(isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container);
  } else{ mountNativeElement(nextVirtualDOM, container); }}function Dome(){
  return <div>Hello</div>
}
function Heart() {
  return <Demo />
}
TinyReact.render(<Heart />, root)
Copy the code

Effect:

Ok, now that we’ve dealt with the function component, let’s deal with the props property of the function component. Just pass virtualdom.props in the virtualdom. type function in the buildFunctionComponent method. When this function receives it in JSX it will be converted to the corresponding node and rendered

function buildFunctionComponent(virtualDOM) {
  return virtualDOM.type(virtualDOM.props || {});
}

function Demo(){
  return <div>Hello</div>
}
function Heart(props) {
  return <div>{props.title}&hearts;<Demo /></div>
}
TinyReact.render(<Heart title="Hello React" />, root)

Copy the code

Now that we’ve covered rendering function components, let’s deal with rendering class components

Class component rendering

Analysis: We said that components handle two things if it’s a function component we call buildFunctionComponent, if it’s a class component we handle a class component function buildClassComponent, We’ll create a base Component class for TinyReact and make all declared components inherit from that Component class. BuildClassComponent like function components, Babel handles class component Type as a function, but that function is the constructor, new to virtualDOM, and then render

import isFunction from "./isFunction";
import isFunctionComponent from "./isFunctionComponent";
import mountNativeElement from "./mountNativeElement";

export default function mountComponent(virtualDOM, container) {
  let nextVirtualDOM = null...else {
    / / class components
    nextVirtualDOM = buildClassComponent(virtualDOM)
  }

  if(isFunction(nextVirtualDOM)) {
    mountComponent(nextVirtualDOM, container);
  } else{ mountNativeElement(nextVirtualDOM, container); }}function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type()
  const nextVirtualDOM = component.render()
  return nextVirtualDOM
}

// Component.js
export default class Component {}// TinyReact/index.js
mport createElement from "./createElement"
import render from "./render"
import Component from "./Component"

export default {
  createElement,
  Component,
  render
}

// SRC /index.js to call Demo
class Alert extends TinyReact.Component {
  render () {
    return <div> Hello TinyReact</div>
  }
}

TinyReact.render(<Alert />, root)

Copy the code

Handles the Props property of the class component

The React class component should look like this

class Alert extends TinyReact.Component {
  render () {
    return <div> 
      {this.props.name}
      {this.props.age}
    </div>
  }
}

TinyReact.render(<Alert name="Zhang" age={20} />, root)
Copy the code

This props property can be initialized in a Component and called in a child Component constructor. The react Component is used as an props property

// Component.js
export default class Component {
  constructor (props) {
    this.props = props
  }
}
class Alert extends TinyReact.Component {
    constructor (props) {
        super(props)
      }
  render () {
    return <div> 
      {this.props.name}
      {this.props.age}
    </div>
  }
}
TinyReact.render(<Alert name="Zhang" age={20} />, root)
Copy the code

All constructers of each class component accept a props parameter that we should pass in buildClassComponent

function buildClassComponent(virtualDOM) {
  const component = new virtualDOM.type(virtualDOM.props || {});
  const nextVirtualDOM = component.render()
  return nextVirtualDOM
}
Copy the code

Good! Today we learn to this, today we mainly learn the VirtualDOM to the real DOM rendering, can also be divided into two categories of common VirtualDOM and component VirtualDOM, we really deal with it or should be divided into three categories, common VirtualDOM, function components and class components VirtualDOM, There are three types of virtualDOM that need to be handled. These three types of virtualDOM have their own differences, but they all need to handle the basic structure first to complete the rendering, and then handle the props in their respective cases. Because class components and function components are converted to it, ordinary VirtualDOM props handle three cases: events, true, false, and NULL text nodes that are not displayed, value of form controls, and checked. Complete the processing of the basic mechanism and props of VirtualDOM, and complete our main goal today, VirtualDOM to RealDOM conversion and rendering

Next we will learn how to update the DOM element, so stay tuned!! Welcome to like and follow, come on