preface

Template => parse AST => splice into render string => New Function Execute render string to generate VNode => patch to generate real DOM

Today we will start with the render string phase, simplify the complexity, extract the core code from the Vue source code, and then write it by hand to render from the Render string to the real DOM

Note:

Dynamic properties are not currently involved, because dynamic properties use the with function to change the scope for data access

Vnode is a data class in Vue, which uses an object to represent a Vnode

I as a result of the expression ability is not good, because will try to speak less, do more…

Handwritten implementation

1. Parsing the DOM

Parse into vnode, render function implementation

Template:

  <div id="app">hello world</div>
Copy the code

The parsed Render string

 _c('div',{attrs:{"id":"app"}},[_v(" hello world ")])
Copy the code

Implement the _v method

function _v(val){
    return {  text: val  }
}
Copy the code

Implement the _c method

Function _c(tag, data, children){ Children if (array.isarray (data)) {children = data data = undefined} return  { tag: tag, data: data, children} }Copy the code

That’s it. The result of executing the render string is

let vnode =  _c('div',{attrs:{"id":"app"}},[_v(" hello world ")])

//vnode:  
{  
    tag: "div", 
    data: { "id":"app" }, 
    children: [ { text: 'hello world' } ]
}
Copy the code

Vnode rendering, patch function implementation

We know that,

hello world

Gets the parent of the current DOM element and the number of elements in which the current DOM element is the parent

function insert(parent, elm, ref) { if (ref ! == undefeind) { parent.insertBefore(elm, ref) } else { parent.appendChild(elm) } } function createElm(vnode, parentElm, refElm) { if (vnode.tag ! Vnode. elm = document.createElement(elm.tag) if (array.isarray (vnode.children)) {for (child of) vnode.children) { createElm(child, vnode.elm, null) } } insert(parentElm, vnode.elm, Vnode.elm = document.createTextNode(vnode.text) insert(parentElm, vnode.elm, parentElm, vnode.elm, refElm) } } function patch(oldvnode,vnode){ if(oldvnode.nodeType ! == undefined){ oldvnode = { tag: oldvnode.tagName.toLowerCase(), elm: ParentElm = document.getelementById ('app'). ParentNode createElm(vnode, ParentElm, document.getelementById ('app').sibling) // Delete template if(parentElm! == null){ parentElm.removeChild(oldvnode.elm) } }Copy the code

test

 let vnode = _c('div', { attrs: { "id": "app" } }, [_c('p', [_v("hello world")])])
 let realDom = patch(document.getElementById('app'), vnode) console.log(realDom); 
//<div><p>hello world</p></div>
Copy the code

section

The _c function simply identifies the tag type and creates the corresponding VNode object. That’s all it does, without messing up the head with the complexity of the Render string.

The patch function recursively traverses the vnode and generates a real DOM save knife inside the vnode. Elm property and mounts it to the page according to the relationship

2. Parse components

Parse into vnode, render function implementation

The template

<div id="app">< /children> </div> //children component children: {template: '<p>children hello world</p>'}Copy the code

The parsed Render string

_c('div',{attrs:{"id":"app"}},[_c('children')]) 
Copy the code

The component is a special carrier, and its contents are all inside, just like a big ball with a small ball. The current resolution is only resolved to div and children tags, but the contents of the component children are not resolved. When will the contents of children be resolved? Here we know that “children” is not a built-in HTML tag, so we need to define an array to distinguish between built-in HTML tags and custom tags, and modify the _c function

Function _c(tag, data, children){ Children if (array.isarray (data)) {children = data data = undefined} + let Vnode = {} + let isHTML = [' div ', 'p', 'span', 'ul', 'li'] + the if (isHTML. Some (tag = > tag)) {+ + vnode = {/ / built-in HTML tags tag: Tag, data: data, children} +}else if(/**){+ // Custom component + vnode = {+ tag: 'Vue-component -'+ global increment ID +tag, + data, + children, + componentOptions: {+ tag, + //Ctor is the children component from the component and a constructor generated by vue.extend + Ctor, + componentInstance: Null //Ctor component is returned after new instantiation object +} +} +}else{+ // neither HTML tag nor component + vnode = {tag, data, children } + } + return vnode - return { tag: tag, data: data, children } }Copy the code

Each component of a Vue is inherited by vue. extend. During vNode generation, if a component’s tag is resolved, the component should be found in the component configuration item. Then obtain the corresponding configuration items of the component data, method, template properties, and then inherit this configuration item to get a component constructor stored in the VNode

let vnode = _c('div',{attrs:{"id":"app"}},[_c('children')]) 
//vnode:
{
    tag: "div",
    data: { attr: { "id": "app" } }
    [
        {
            tag: "vue-component-1-children",
            data: null,
            children: null
            componentOptions: {
                tag: "children",
                Ctor,
                componentInstance: null
            }
        }
    ]
}
Copy the code

Vnode rendering, patch function implementation

CreateElm function createElm function

function createElm(vnode, parentElm, refElm) { + if(vnode.componentOptions ! = = null) {+ let child. = new vnode.com ponentOptions Ctor (vnode) + / / into the child's template parsing stage + child. $mount (vnode. Elm) +} if (vnode.tag ! Vnode. elm = document.createElement(elm.tag) if (array.isarray (vnode.children)) {for (child of) vnode.children) { createElm(child, vnode.elm, null) } } insert(parentElm, vnode.elm, Vnode.elm = document.createTextNode(vnode.text) INSERT (parentElm, vnode.elm, refElm)} else {// text node vnode.elm = document.createTextNode(vnode.text)}Copy the code

CreateElm recursively parses a vNode. If you encounter a new constructor between components and call its mount method, mount method, mount method, which is derived from vue. extend, then you’re in the component’s parsing environment. All the way down to the deepest component, so that’s why Vue’s live hook function is:

Created (parent) Created (child 1) Created (child 2) Mounted (child 2) Mounted (child 1) Mounted (parent

Using a brain map to represent the parsing process is:

Conclusion:

This is a brief introduction to how components in Vue are rendered into DOM elements. This is a brief introduction to how components are rendered, and does not include components’ values, slots, etc. These are just additional content, because there is a parent-child relationship when creating a Vnode. Slots only need to get the corresponding data through the parent-child relationship, while dynamic properties, events, and life hooks of DOM are the most complete properties stored in VNode, so they only need to parse their properties at the corresponding stage