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

The next lesson is about updating DOM elements, VirtualDOM comparison. The rendering work from virtual DOM to real DOM was completed before, and then the Diff algorithm stage was entered.

The node types are the same

We prepare two pieces of JSX, then render them back and forth using the tinyReact. render method we wrote earlier, and overwrite the DOM generated by the previous JSX with the last piece of JSX. The second JSX differs from the first JSX in attributes and content nodes. We complete the Virtual comparison to achieve minimal DOM updates

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>
)
Copy the code

There are different attributes, there are different node contents, there are different functions that respond with processing time, but the node type remains the same

const modifyDOM = (
  <div className="container">
    <h1>Hello Tiny React</h1>
    <h2 data-test="test123">(Code killer)</h2>
    <div>Nested 1<div>Nested 1.1</div>
    </div>
    <h3>(Observation: This will change)</h3>{= = 1 & 2<div>If 2 is equal to 1, change the current content</div>}
    {2 == 2 && <div>2</div>}
    <span>This is a paragraph</span>
    <button onClick={()= >Alert (" Hello, I've updated ")}> Click me</button>
    <h3>This will be deleted</h3>
    2, 3
    <input type="text" value="13" />
  </div>
);
TinyReact.render(virtualDOM, root)

setTimeout(() = > {
  TinyReact.render(modifyDOM, root)
}, 2000)
Copy the code

Virtual DOM than

Question:

We are going to face the first problem. When performing VirtualDOM comparison, we need to use the updated VirtualDOM and the pre-updated VirtualDOM. The updated VirtualDOM can be passed by the Render method at present. But how do you get old VirtualDOM before it’s updated?

Analysis:

In this case, we can store VirtualDOM in the real DOM object properties when we create the real DOM object. Before the VirtualDOM comparison is performed, the corresponding VirtualDOM object can be obtained from the real DOM object, which is actually obtained from the third parameter of the Render method.

When we write JSX code, there must be a parent root node for each piece of JSX code. This container. FirstChild is the parent node for each piece of JSX code

<div id="app"></div> // container
// a paragraph of JSX 
      
/
/ container.firstChild js</span> </div> Copy the code

Solution:

Add the corresponding VirtualDOM object when you create the real DOM object

The key code

// createDOMElement.js
import mountElement from "./mountElement"

export default function mountNativeElement(virtualDOM, container) {
    // Mount VirtualDOM to the properties of the real DOM object for easy access to its VirtualDOM for comparison
    newELement._virtualDOM = virtualDOM
}
Copy the code

As shown above, we used the current page to render real DOM objects to store the old VirtualDOM. Now let’s compare with the same VirtualDOM type where we finished updating the text nodes and node properties.

1. If it is a text node, we update its contents and create an updateTextNode method to process it.

    export default function updateTextNode(virtualDOM, oldVirtualDOM, oldDOM) {
  if(virtualDOM.props.textContent ! == oldVirtualDOM.props.textContent) {// Update the corresponding DOM content
    oldDOM.textContent = virtualDOM.props.textContent
    // Will be the latest
    oldDOM._virtualDOM = virtualDOM
  }

}
Copy the code

The code above is a comparison of text nodes, but now that we have only taken the first layer of the DOM, we need to compare the child nodes below. We need to iterate over the recursive child elements and call diff to compare the child elements

2. If it’s an element node, we update its attribute value, which we do by creating the updateNodeElement method

We created the updateNodeElement method when we set element attributes. Since setting and updating elements are similar to element node attributes, we use this method to encapsulate them. The first parameter is the DOM element to update, and the second parameter is the virtualDOM to update. The third is oldVirtualDOM. Note: oldVirtualDOM is not passed to set element node attributes, it is passed to update element node attributes.


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

  Object.keys(newProps).forEach(propName= > {
    // Get the attribute value
    const newPropsValue = newProps[propName]
    const oldPropsValue = oldProps[propName]
    if(newPropsValue ! == oldPropsValue) {if (propName.slice(0.2) = = ="on") {
        OnClick => click

        const eventName = propName.toLowerCase().slice(2);

        // Add events to the element
        newElement.addEventListener(eventName, newPropsValue);

        // Delete the event handler for the original event
        if(oldPropsValue) {
          newElement.removeEventListener(eventName, oldPropsValue)
        }
      } 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

newPropsValue ! OldPropsValue == oldPropsValue == oldVirtualDOM == oldPropsValue == oldVirtualDOM == oldPropsValue == oldVirtualDOM

In addition to updating an element node attribute, we also have the option of deleting an element node attribute, how do we know that the element node attribute has been deleted if the value of the new node attribute is empty, then the attribute has been deleted. There are two cases: when an event is deleted, we use the removeEventListener method to deregister the event; when other attributes are deleted, we directly use the removeAttribute method to delete the attribute.

export default function diff (virtualDOM, container, oldDOM) {...// Clear the deleted node
  Object.keys(oldProps).forEach(propName= > {
    const newPropsValue  = newProps[propName]
    const oldPropsValue = oldProps[propName]
    if(! newPropsValue) {// Attribute deleted
      if(propName.slice(0.2) = = ="on") {
        const eventName = propName.toLowerCase().slice(2);
        newElement.removeEventListener(eventName, oldPropsValue);
      } else if(propName ! = ="children") {
        newElement.removeAttribute(propName)
      }
    }
  })
}
Copy the code

Here we are, with the same nodes, updating the DOM’s text nodes and node element attributes to a minimum extent.

3 summary:

We started with two pieces of JSX code in SRC/Index. We executed and rendered the first piece of JSX code when the page loaded, and then executed the second piece of JSX code two seconds later.

Since there are already old DOM objects in the page, we can’t render the second piece of code directly in the page. At this time, we need to compare the new VirtualDOM with the old VirtualDOM. During the comparison process, we can find the differences and update the differences to the page. This enables minimal DOM updates.

To complete the comparison, we need to get the new and old VirtualDOM. The new VirtualDOM is executed after JSX code modifyDOM is delayed for 2 seconds. Now the question is how do we get the old VirtualDOM? We know that the render DOM of the page is our original DOM, and the new DOM is definitely available. Can we use the actual DOM of the page to obtain the corresponding old VirtualDOM? Since this method converts VirtualDOM into a real DOM object, Newelement. _virtualDOM = virtualDOM. During comparison, the corresponding virtualDOM object will be found through the corresponding real DOM. So render uses the third argument oldDOM, which points to the old, real DOM object on the page, obtained from container. FirstChild, because container is a div element with the id root. The actual DOM objects we rendered are added to the div, and since every JSX has a parent tag, we can retrieve the old DOM objects through the Container’s firstChild.

After passing the new VirtualDOM object, container, oldDOM to the diff method and finding the diff method, 1. If oldDOM exists, we will obtain the corresponding VirtualDOM. If oldDOM exists, we will not go through the direct rendering method described in the previous section, but execute the code to compare DOM updates. 2. In the execution of the update code, the comparison is divided into a variety of situations, one is the text node, one is the element node. In the case of text, we update the text content, and in the case of nodes we update the element attributes (the updates are divided into two categories: the value of the attribute changes, or the attribute is deleted). 3. Step 2 We need to recurse child nodes continuously to complete the text update of all child nodes and the update of element node attributes. Child is the child element to be updated, oldDOM is the container, and oldDOM. ChildNodes [I] is the comparison child.

This completes the minimal update to the page DOM.

Look at the picture than

We can take a look at this image to see how the two VirtualDOM pairs are compared.

The image is split into two parts, with the left representing the unupdated VirtualDOM and the right representing the updated VirtualDOM. As you can see from the figure, VirtualDOM performs peer comparison, child to child comparison, and parent to parent comparison. There will be no cross-level alignment. If the two nodes are of the same type, see what type they are. If they are of text type, compare the two text nodes to see if they are the same. If they are different, the new text node replaces the old text node. If it is an element node, it needs to compare whether the attributes of the new node and the old node are the same. If they are the same, no processing is done. If they are not the same, the new node attribute value is used to replace the old node attribute value. If the new node has a deleted attribute, how to know which node is deleted? Use the old node attribute name to obtain the attribute value of the new node, if not, the attribute is deleted.

Let’s look at the second image:

When the old and new DOM are compared, depth-first is adopted, that is, the comparison of child nodes takes precedence over the comparison of peer nodes. The UL node will be compared first, and the first LI child node will be compared after the comparison is completed. After the comparison, the li child node will be checked, and the first P node of the child node will be compared, and the second Li node will be compared when the P node has no child node. In our code, we recursively call the diff method in the loop body. When all the children of a node are compared, we jump back to the peer node for comparison. If the child node has any children, we perform the same process to recursively compare the child nodes, and after comparison, we jump back to downgrade one peer node

Comparison update: Only peer comparison and depth-first comparison are performed to complete DOM minimization update in the case of the same node type.

Node types are different

When the node type is different, you don’t need to compare. You just need to generate new different nodes and replace the old DOM object with the new DOM object.

export default function diff (virtualDOM, container, oldDOM) {...}else if( virtualDOM.type ! == oldVirtualDOM.type &&typeofvirtualDOM ! = ="function") {
      const newElement = createDOMElement(virtualDOM)
      oldDOM.parentNode.replaceChild(newElement, oldDOM)

  } else if(oldVirtualDOM && virtualdom.type === oldVirtualdom.type) {···Copy the code

Deleting a node

We need to know the highlights:

  1. Deleting a node occurs after the node is updated,
    • After the DOM comparison is updated, we go back to analyzing which nodes are to be deleted
  2. Node deletion occurs to all children of the same parent node
    • That is, node deletion occurs within a certain range

How do we know which node has been deleted? If the number of old node objects exceeds the number of VirtualDOM objects for the new node after the node is updated, a node needs to be deleted. We can look at the next image:

The UL on the left is the old DOM object, and the UL on the right is the new UL object. To know which ones have been deleted, we need to compare all the li child nodes on the UL face. Comparing UL is ok, then comparing li, the first li child node, finds that the node type is the same and the text nodes are all 1, no problem. It is found that the text of the second node is different, the old one is 2, and the new one is 3. In this case, the old 2 will be replaced with this 3. Similarly, it is found that the content of the third node is still different, so the text node will be replaced. This is after the new node is compared, the old node has one more node object, so the last node that is not used is a deleted node. We first use the index to compare, and then we use the key to compare.

Code:

    // Get the number of old nodes

    let oldChildNodes = oldDOM.childNodes

    // If the number of kink points is greater than the length of new nodes to render
    if(oldChildNodes.length > virtualDOM.children.length) {
        for(let i = oldChildNodes.length -1; I > virtualDOM. Children. length -1; i--) { oldDOM.removeChild(oldChildNodes[i])// This code is extracted into a delete method unmountNode method}}Copy the code

How do you know if a node has been deleted after it has been updated and all children of the same parent node have been deleted? The number of old nodes is greater than the number of new nodes ready to render.

Today we have completed THE DOM comparison of the same and different node types. When the node types are the same, we will use the old and new nodes and use depth-first peer comparison to complete the update of text nodes and node attributes. When the nodes are different, we will complete the nodes. You also learned about node deletion later. On the sixth day we learn component update, using setState method to complete the update of the class component, in the not the same component and is the same component how to complete the update, please look forward to!!

Dreams ---- Furukawa Shuntaro at night ancient memories weave my dreams so dreams fall into the abyss for a long time and the rain does not stop in the small setbacks I seek simple languageCopy the code