See other chapters:
Template
Mentioned earlier Virtual dom in vue Mainly do two things:
- Provide the vNode corresponding to the real node
- 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:
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.
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:
stay The role of virtual dom In, we know the location of the virtual dom:
Finally, we can combine the two diagrams into one:
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:
- 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
- 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
- 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: