VueAs one of the three popular front-end frameworks, the easy-to-use feature is loved by everyone, and has always been the best tool for business development projects. However, the light will certainly not be used, do not understand the framework of internal implementation and design ideas, in the use of the process of problems will inevitably be a little confused. Since ancient times, there have been clouds, and we know why. Let’s take a step-by-step look at the core principles of the Vue framework from simple examples.

The main topic of the author usually study collation, the content is divided into two chapters, this is the first chapter.

1. Three core blocks

As we all know,VueThe framework is composed of three core plates, respectivelyResponsive plate.Compile the plateandApply colours to a drawing plate.

  • Reactive block: Use JavaScript to build reactive data objects from component instances
  • Compile block: Pass the contents of the template to the render function and return the vNode.
  • Render plate: Accepts vNodes and renders them into the real DOM by comparison

Because Vue2.0 and Vue3.0 data objects used to build the response type, the different ways. 2.0 is the Object defineProperty, 3.0 is the proxy. There is a lot of content, so the response plate will be explained in the next chapter, which will explain the latter two plates first.

2. Simple implementation of compilation and rendering plates

2.1 Background and significance of the emergence of framework

Without further ado, get to the point. The title of the article is “understandable”, so here is a basic example to do, I believe that it can help you understand the framework principle.

Let’s say we just implement the simplest thing and display one on the pageHello World. The following information is displayed:

If we were to do this in the context of an era without a framework. Create a label, modify its textContent, and color it:

let iDiv = document.createElement('div')
iDiv.textContent = "Hello World";
iDiv.style.color = "red";
document.body.appendChild(iDiv)
Copy the code

There’s nothing wrong with that, and in the era of jQuery and associated apis, the front-end industry is thriving. Over time, however, some problems became apparent. Mainly summarized as the following items:

  • Frequent manipulation of DOM elements: Pages are made up of element tags, and updating element content requires frequent manipulation of computed element content;
  • Low data update efficiency: the changed data is large or small, and there is no good update strategy for mass data update or a small amount of data change in multi-tier DOM structure;
  • Lack of modularity: This refers to HTML modularity, although the W3C’s Web Components standard is intended to support HTML modularity in its own right, but it is not yet complete and not supported by all major browsers.

In view of the above three problems, we can think and analyze and try to find solutions. First of all, the first and third questions can be grouped together. They are both about HTML. We know that the attribute values on the native element tag are many, and a single div tag contains dozens of attribute values:

This content is generated when we call document.createElement, and we only use the textContent and style attributes. Can we build an object that only contains the content we care about? The answer is yes, and the concept of Virtual DOM, which uses JS objects to simulate the real DOM structure, and JS also supports modularity. Secondly, in view of the second problem, a variety of DOM diff algorithms are implemented on the basis of virtual DOM to improve the update efficiency.

To summarize, the framework uses the virtual DOM to mimic the real DOM, and internally modularizes the DOM while rendering data and updating it efficiently. Developers just need to call the API to pass and assign the relevant data.

3. Code practice

Based on the above, the following code contains implementations of the Vue framework’s three main functions:

  • Render function, alias h; Used to build vNodes
  • Mount function to render vNode as a real node
  • Patch function for update operation

3.1 Basic Structure

Again, assume the following basic structure:

<style>
  .red {color: #f00;font-size:24px; }</style>
<div id="app"></div>
Copy the code

The concrete implementation of each method follows.

3.2 Render method

Now render Hello World inside the div! . Those of you who have used Vue should know how to use h method:

const vdom = h('div', {class:'red'},[
  h('span'.null['hello']])Copy the code

The h method here takes three arguments and returns vNode. Following the principle of simple implementation, we built the virtual DOM structure as simply as possible:

/** * tag: element tag * props: element related attributes * children: element child **/
function h(tag,props,children){
    return {
        tag,
        props,
        children
    }
}
Copy the code

The job of this method is to convert template elements to vNodes and return. You must have used the template syntax in a single file component instance of Vue. Inside the Vue framework, the template content is parsed (parse, optimize, generate) as a string, passed in and returned as a VNode object. There’s a lot of extra work to do, of course, but interested students can read the source code. This is just the core idea.

3.3 mount method

Then we render the vNode into the real DOM:

mount(vdom,document.getElementById('app'))
Copy the code

You can see that the mount method takes two parameters to mount the VNode to the actual DOM node.

function mount(vnode,container){
    // Pass the container layer by layer
    const el = vnode.el = document.createElement(vnode.tag);
    / / props
    if(vnode.props){
        for(const key in vnode.props){
            const value = vnode.props[key];
            // Props has a number of types, such as directives, class names, and methods, which are assumed to be attributes
            el.setAttribute(key,value)
        }
    }
   / / deal with children
   if(vnode.children){
     // Assume that the child node is a string
     if(typeof vnode.children === 'string'){
         el.textContent = vnode.children;
     }else{ 
         vnode.children.forEach(child= >{
             mount(child,el)
         })
     }
   } 
   container.appendChild(el)
}
Copy the code

Step by step analysis of code content:

  • Each VNode carries a tag attribute that allows you to create real DOM element tags and assign each vNode a new attribute, EL, to store its real DOM structure
  • Then handle the PROPS for the Vnode. Based on simple implementation principles, we assume that the elements props only involve attributes, so we traverse the props object and set all data to the attr of the node EL.
  • We then process the children of vNode in two cases: when the child node is a text node, we directly set it toel.textContentCan; When the child nodes are arrays, all the child elements are iterated, and the current EL is used as a container to recursively call mount to render all the child nodes.
  • Finally, mount the EL node into the original Container, as in this case<div id="app"></div>

Clean up the code, you can get the desired effect in the page (the following code directly copy and paste can be run) :

<html>
<style>
  .red {color: #f00;font-size:24px; }</style>
<div id="app"></div>
<script>
  const vdom = h('div', {class:'red'},[
    h('span'.null['hello'])
  ])
  mount(vdom,document.getElementById('app'))
  
  function h(tag,props,children){
    return {
        tag,
        props,
        children
    }
  }
  function mount(vnode,container){
    // Pass the container layer by layer
    const el = vnode.el = document.createElement(vnode.tag);
    / / props
    if(vnode.props){
        for(const key in vnode.props){
            const value = vnode.props[key];
            // Props has a number of types, which are assumed to be attributes
            el.setAttribute(key,value)
        }
    }
   / / deal with children
   if(vnode.children){
     // Assume that the child node is a string
     if(typeof vnode.children === 'string'){
         el.textContent = vnode.children;
     }else{ 
         vnode.children.forEach(child= >{
             mount(child,el)
         })
     }
   } 
   container.appendChild(el)
  }
</script>
</html>
Copy the code

3.4 patch method

Project development, page data can not be invariable, update operation is particularly important. In the spirit of simple implementation basics, suppose we now want to change the page text toHello VueAnd change the color to look like this:

When the developer uses the Vue framework to do this, it should change the class name of the element tag and update the text content in the element. Inside the Vue framework, a virtual DOM tree is regenerated from this, which might look like this:

const vdom2 = h('div', {class:'green'},[
    h('span'.null.'Hello Vue! ')])Copy the code

After the operation is complete, the internal patch method will be triggered. Because two virtual DOM trees are generated, patch method is bound to compare them. Based on the principle of simple implementation, this paper only considers the comparison of nodes of the same type (Vue source code will be compared by key value and whether it is a static node and other information, interested students can read the source code) :

function patch(n1,n2){
  // Assume that the node types have not changed before and after
  if(n1.tag === n2.tag){}
}
Copy the code

Next, process the data step by step.

  • Handle props first:
const el = n2.el = n1.el;
// 1. Handle props
const oldProps = n1.props ||{};
const newProps = n2.props || {};
// Iterate over all data for the new attribute
for(const key in newProps){
    const oldValue = oldProps[key];
    const newValue = newProps[key];
    // If the value of the new attribute is different from that of the old one, set the new attribute to the current element
   if(newValue ! == oldValue){ el.setAttribute(key,newValue) } }// Iterate over all data of the old attribute
for(const key in oldProps){
    // If there is no corresponding attribute in the new structure, the corresponding attribute value should be removed
    if(! (keyin newProps)){
        el.removeAttribute(key)
    }
}
Copy the code

The code comments have been written more clearly. One caveat: This code compares the contents of the new and old properties, based on the fact that the old and new trees are the same nodes. For the new attribute, check to see if there is a different value from the old attribute, and change the assignment if there is. For the old attribute, traverse to see if there is a value that is not in the new attribute. If not, remove it.

  • Then handle the child node, this content is quite large, let’s first sort out the idea:

1. When the new node is a string, there are two cases to handle: the old node is a string and the old node is not a string; 2. When the new node is not a string, there are also two cases to handle: the old node is a string and the old node is not a string;

Deal with the first step:

// 2. Process child nodes
const oldChildren = n1.children;
const newChildren = n2.children;
// 2.1 If the new child node is a string
if(typeof newChildren === 'string') {// If the old child is also a string
    if(typeof oldChildren === 'string') {// If the contents of the old and new nodes are different, the new value is assigned to
        if(newChildren !== oldChildren){
            el.textContent = newChildren;
        }
    }else{
        // The old child node is not a string. The new child node is a stringel.textContent = newChildren; }}else{}Copy the code

If the new node is a string, it’s easier to handle it. Whether the old node is a string or not, we’ll just replace the new node with textContent. The condition is whether the content of the old and new nodes has changed. The details are clearly written in the code comments, so I won’t repeat them here.

Next, handle the second step (following the code) :

// 2. Process child nodes
const oldChildren = n1.children;
const newChildren = n2.children;
// 2.1 If the new child node is a string
if(typeof newChildren === 'string'){
...
}else{
  2.2 If the new child node is not a string
  // But the old child node is a string
  if(typeof oldChildren === 'string'){
      el.innerHTML = ' '; // Empty the element content to iterate over and render the new child nodes
      newChildren.forEach(child= >{
        mount(child,el); // Render the child nodes one by one under the el element})}else{
      // 2.3 The new and old child nodes are not strings
      const commonLength = Math.min(oldChildren.length,newChildren.length);
      // For all common nodes, compare recursively
      for(let i=0; i<commonLength; i++){ patch(oldChildren[i],newChildren[i]) }// More new nodes, add the extra nodes
      if(newChildren.length > oldChildren.length){
          newChildren.slice(oldChildren.length).forEach(child= >{
            mount(child,el)
          })
      }
      // There are more old nodes, remove the extra nodes
      if(oldChildren.length > newChildren.length){
          oldChildren.slice(newChildren.length).forEach(child= >{
            el.removeChild(child.el)
          })
      }
  }
}
Copy the code

To read this code, the new node is not a string, but an array containing the contents of multiple nodes:

  • The old node is a string: the innerHTML of the current node is empty, and mount is called to render each child node onto the EL element.
  • The old node is not a string, indicating that it is now a comparison of two array objects.
    • First, take the minimum length of the new and old child nodes, and first deal with the node content they share. Specifically, common elements are recursively called patch and then compared to see how different each child element is. The extra child nodes will be processed later.
    • Then, if the number of new nodes is large, add the traversal of the additional child nodes to the current EL.
    • If the number of new nodes is small, remove the excess content of the old node.

At this point, all compilation and rendering, as well as node updates, are explained. Of course, the above code is assumed to be ideal, but in line with the principle of simple implementation, I hope you can have a clear understanding of the basic principles of Vue framework through reading practice, so the purpose of this article has been achieved.

Finally, all of the above code has been briefly organized and put in codePen, so you can look at it. It’s not very powerful, it’s not a very sophisticated diff algorithm, but now you can confidently say that you can write a rendering framework by hand (even if it’s rudimentary, hahaha).

Code link: Implement Vnode by hand

Thanks for reading.

Photo source:dribbble.com/mappleton