vue quick start series - templates

See other chapters:

vue quick start series

Template

Mentioned earlier Virtual dom in vue Mainly do two things:

  1. Provide the vNode corresponding to the real node
  2. Compare the old and new vnodes, find the differences, and then update the view

① Where does vNode come from?

As mentioned earlier, the declarative framework only needs us to describe the mapping relationship between state and dom. The framework will give us the transition from state to view.

② What is used to describe the mapping relationship between state and dom?

Tip: jQuery is an imperative framework. Modern vue and react are declarative frameworks.

brief introduction

First, publish the answer to question ②: use a template to describe the mapping relationship between state and dom.

So we know the relationship between the three:

graph LR Status -- > template -- > DOM

Template compiler

Let's take a look at an example of a template:

<span>Message: {{ msg }}</span>

<h1 v-if="awesome">Vue is awesome!</h1>

<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>

What are v-if, v-for, {}}? These things don't exist in html at all.

We know that javascript code is only recognized by javascript engine. Similarly, templates are only recognized by things similar to template engine.

In vue, a similar template engine is called a template compiler. The template is compiled into a rendering function through the template compiler, and the execution of the rendering function will generate a vnode using the current latest state.

graph LR Template -- compile -- > render function -- execute -- > vnode

So far, the answer to question ① is obvious. vNode is generated by the rendering function.

Location of the template and virtual dom

According to the above, we can easily know the location of the template:

flowchart LR Status -- > template subgraph a [template] Template -- compile -- > render function -- execute -- > vnode end Vnode -- > View

stay The role of virtual dom In, we know the location of the virtual dom:

flowchart LR Status -- > A subgraph a [virtual dom] vNode patch end A -- > View

Finally, we can combine the two diagrams into one:

flowchart LR Status -- > template subgraph a [template] Template -- compile -- > render function end Render function -- execute -- > b subgraph b [virtual dom] vNode patch end B -- > View

Tip: point the rendering function to the virtual DOM because there is a sentence on the Vue official website: "virtual DOM" is our name for the entire VNode tree established by the Vue component tree

How is the template compiled into a rendering function, and why can vNode be generated by executing the rendering function? Please continue to read below.

Rendering function

To compile the template into a rendering function, you only need 3 steps:

  1. Parser: convert HTML string to AST
    • AST is a common javascript object that describes the information of this node and the information of child nodes, similar to vNode
  2. Optimizer: traverse AST and mark static nodes to improve performance
    • <p>Hello < / P > is a static node and will not change after rendering
    • <p>{{Hello} < / P > is not a static node because the state affects it
  3. Generators: generating render functions using AST
    • Executing the rendering function will generate a virtual dom (vNode) according to the current state

Why these three steps? It doesn't matter. It's just an algorithm.

Tip: if we can understand that these three steps can indeed compile the template into a rendering function, and the rendering function can generate vNode after execution. Then the template part of vue can be regarded as an introduction.

analysis

We take the most direct approach, that is, run a piece of code to see what AST is? What does the optimizer do? What is the rendering function? How does the rendering function generate vNode?

The code is very simple, an HTML page, which introduces Vue JS, and then in Vue JS (enter debugger), and finally run test html:

// test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src='vue.js'></script>
</head>
<body>
<!-- Template -->
<div id='app'>
    <p title='testTitle' @click='say'>number: {{num}}</p>
</div>
<!-- /Template -->

<script>
const app = new Vue({
    el: '#app',
    data: {
        num: 0
    },
    methods: {
      say(){
        this.num += 1;
      }
    }
})
</script>
</body>
</html>
// vue.js
// Break point (line {1})

  var createCompiler = createCompilerCreator(function baseCompile (
    template,
    options
  ) {
    debugger // {1}
    // Parser
    var ast = parse(template.trim(), options);
    if (options.optimize !== false) {
      // optimizer
      optimize(ast, options);
    }
    // generator 
    var code = generate(ast, options);
    return {
      ast: ast,
      render: code.render,
      staticRenderFns: code.staticRenderFns
    }
  });

AST

After executing var ast = parse(template.trim(), options);, AST is:

// ast:
{
    "type":1,
    "tag":"div",
    "attrsList":[
        {
            "name":"id",
            "value":"app"
        }
    ],
    "attrsMap":{
        "id":"app"
    },
    "children":[
        {
            "type":1,
            "tag":"p",
            "attrsList":[
                {
                    "name":"title",
                    "value":"testTitle"
                },
                {
                    "name":"@click",
                    "value":"say"
                }
            ],
            "attrsMap":{
                "title":"testTitle",
                "@click":"say"
            },
            "children":[
                {
                    "type":2,
                    "expression":"'number: '+_s(num)",
                    "tokens":[
                        "number: ",
                        {
                            "@binding":"num"
                        }
                    ],
                    "text":"number: {{num}}"
                }
            ],
            "plain":false,
            "attrs":[
                {
                    "name":"title",
                    "value":"testTitle"
                }
            ],
            "hasBindings":true,
            "events":{
                "click":{
                    "value":"say"
                }
            }
        }
    ],
    "plain":false,
    "attrs":[
        {
            "name":"id",
            "value":"app"
        }
    ]
}

So we know that AST is an ordinary javascript object, similar to virtual node or dom Node , which contains node types, attributes, child nodes, etc.

Role of optimizer

After the ast is handed over to the optimizer for processing (optimize(ast, options);), ast is:

// Optimizer: (add static and staticRoot attributes on the basis of the previous step)
{
    "type":1,
    "tag":"div",
    "attrsList":[
        {
            "name":"id",
            "value":"app"
        }
    ],
    "attrsMap":{
        "id":"app"
    },
    "children":[
        {
            "type":1,
            "tag":"p",
            "attrsList":[
                {
                    "name":"title",
                    "value":"testTitle"
                },
                {
                    "name":"@click",
                    "value":"say"
                }
            ],
            "attrsMap":{
                "title":"testTitle",
                "@click":"say"
            },
            "children":[
                {
                    "type":2,
                    "expression":"'number: '+_s(num)",
                    "tokens":[
                        "number: ",
                        {
                            "@binding":"num"
                        }
                    ],
                    "text":"number: {{num}}",
                    "static":false
                }
            ],
            "plain":false,
            "attrs":[
                {
                    "name":"title",
                    "value":"testTitle"
                }
            ],
            "hasBindings":true,
            "events":{
                "click":{
                    "value":"say"
                }
            },
            "static":false,
            "staticRoot":false
        }
    ],
    "plain":false,
    "attrs":[
        {
            "name":"id",
            "value":"app"
        }
    ],
    "static":false,
    "staticRoot":false
}

The optimizer adds static and staticRoot attributes to the ast to mark static nodes.

generator

Then, the ast is handed over to the generator for processing (var code = generate(ast, options);), Code is:

// code
{"render":"with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_c('p',{attrs:{\"title\":\"testTitle\"},on:{\"click\":say}},[_v(\"number: \"+_s(num))])])}","staticRenderFns":[]}

Add code Render string formatting:

// code.render
with(this) {
    return _c(
        'div', 
        {
            attrs: {
                "id": "app"
            }
        },
        [
            _c(
                'p', 
                {
                    attrs: {
                        "title": "testTitle"
                    },
                    on: {
                        "click": say
                    }
                },
                [
                    _v("number: " + _s(num))
                ]
            )
        ]
    )
}

code. The render string is exported to the outside world and will be put into a function, which is the rendering function.

incomprehension? Never mind. Let's look at another example first:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

const obj = {name: 'ph'}
const code = `with(this){console.log('hello: ' + name)}`
const renderFunction = new Function(code)
renderFunction.call(obj)

// Equivalent to

const obj = {name: 'ph'}
function renderFunction(){
  with(this){console.log('hello: ' + name)}
}
renderFunction.call(obj) // hello: ph

Now understand. We will code The string pointed to by render is exported to the outside world, and the outside world uses new Function() to create a rendering function.

As mentioned earlier, executing the rendering function will generate vNode. Look at code Render will know what's inside_ v and_ c. It is used to generate vNode of element type and vNode of text type respectively. Please see the relevant source code:

// Create a text type vNode
target._v = createTextVNode;
function createTextVNode (val) {
    return new VNode(undefined, undefined, undefined, String(val))
}

// Create vNode of element type
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
function createElement (
    context,
    tag,
    data,
    children,
    normalizationType,
    alwaysNormalize
  ) {
    ...
    return _createElement(context, tag, data, children, normalizationType)
  }

Tip: this series will not expand on how to implement the parser, optimizer and generator in vue.

See other chapters:

vue quick start series

Keywords: Vue

Added by slands10 on Wed, 12 Jan 2022 19:51:26 +0200