vue 快速入门 系列 —— 模板
2022/1/13 6:07:31
本文主要是介绍vue 快速入门 系列 —— 模板,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
其他章节请看:
vue 快速入门 系列
模板
前面提到 vue 中的虚拟 dom 主要做两件事:
- 提供与真实节点对应的 vNode
- 新旧 vNode 对比,寻找差异,然后更新视图
①、vNode 从何而来?
前面也说了声明式框架只需要我们描述状态
与 dom
之间的映射关系。状态
到视图
的转换,框架会给我们做。
②、用什么描述状态与 dom 之间的映射关系?
Tip:jQuery 是命令式的框架,现代的 vue、react属于声明式框架。
简介
首先公布问题 ② 的答案:用模板描述状态与 dom 之间的映射关系。
于是我们知道这三者之间的关系:
graph LR 状态 --> 模板 --> dom模板编译器
请先看一个模板的示例:
<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>
v-if
、v-for
、{{}}
是什么?html 中根本不存在这些东西。
我们知道 javascript 代码只有 javascript 引擎认识,同理,模板也只有类似模板引擎的东西认识它。
在 vue 中,类似模板引擎的叫做模板编译器。通过模板编译器将模板编译成渲染函数,而执行渲染函数就会使用当前最新的状态生成一份 vnode。
graph LR 模板 -- 编译 --> 渲染函数 -- 执行 --> vNode至此,问题 ① 的答案显而易见了,vNode 由渲染函数生成。
模板和虚拟 dom 所处位置
我们根据上文,能轻易的知道模板
所处位置:
在 虚拟 dom 的作用 中,我们知道虚拟 dom
所处位置:
最后,我们将这两个图合并成一个即可:
flowchart LR 状态 --> 模板 subgraph a[模板] 模板 -- 编译 --> 渲染函数 end 渲染函数 -- 执行 --> b subgraph b[虚拟 dom] vNode patch end b --> 视图Tip: 将渲染函数指向虚拟 dom
,是因为 vue 官网有这么一句话:“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼
模板是如何编译成渲染函数,以及为什么执行渲染函数就可以生成 vNode?请继续看下文。
渲染函数
将模板编译成渲染函数,只需要 3 步:
- 解析器:将HTML字符串转换为
AST
AST
就是一个普通的 javascript 对象,描述了该节点的信息以及子节点的信息,类似 vNode
- 优化器:遍历 AST,标记静态节点,用于提高性能
<p>hello</p>
是静态节点,渲染之后不会再改变<p>{{hello}}</p>
不是静态节点,因为状态会影响它
- 生成器:使用
AST
生成渲染函数
- 执行渲染函数就会根据现在的状态生成一份虚拟 dom(
vNode
)
- 执行渲染函数就会根据现在的状态生成一份虚拟 dom(
为什么是这 3 步?不重要,这只是一种算法而已。
Tip:倘若我们能理解这 3 步确实能将模板编译成渲染函数,而渲染函数执行后能生成 vNode。那么 vue 中模板这一部分,也算是入门了。
分析
我们采用最直接的方法,即运行一段代码,看看 AST
是什么?优化器
做了什么?渲染函数
是什么?渲染函数又是如何生成 vNode
的?
代码很简单,一个 html 页面,里面引入 vue.js
,然后在 vue.js
中打上一个断点(输入 debugger),最后运行 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> <!-- 模板 --> <div id='app'> <p title='testTitle' @click='say'>number: {{num}}</p> </div> <!-- /模板 --> <script> const app = new Vue({ el: '#app', data: { num: 0 }, methods: { say(){ this.num += 1; } } }) </script> </body> </html>
// vue.js // 打上断点(行{1}) var createCompiler = createCompilerCreator(function baseCompile ( template, options ) { debugger // {1} // 解析器 var ast = parse(template.trim(), options); if (options.optimize !== false) { // 优化器 optimize(ast, options); } // 生成器 var code = generate(ast, options); return { ast: ast, render: code.render, staticRenderFns: code.staticRenderFns } });
AST
执行完 var ast = parse(template.trim(), options);
,ast
为:
// 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" } ] }
于是我们知道,AST
就是一个普通的 javascript 对象,类似虚拟节点或 dom Node,里面有节点的类型、属性、子节点等等。
优化器的作用
将 ast 交给优化器处理后(optimize(ast, options);
),ast
为:
// 优化器:(在上一步的基础上增加 static 和 staticRoot 两个属性) { "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 }
优化器给 ast
增加 static
和 staticRoot
两个属性,用于标记静态节点。
生成器
接着将 ast
交给生成器处理(var code = generate(ast, options);
),code
为:
// code {"render":"with(this){return _c('div',{attrs:{\"id\":\"app\"}},[_c('p',{attrs:{\"title\":\"testTitle\"},on:{\"click\":say}},[_v(\"number: \"+_s(num))])])}","staticRenderFns":[]}
将 code.render
字符串格式化:
// code.render with(this) { return _c( 'div', { attrs: { "id": "app" } }, [ _c( 'p', { attrs: { "title": "testTitle" }, on: { "click": say } }, [ _v("number: " + _s(num)) ] ) ] ) }
code.render
这个字符串导出到外界,会放到一个函数中,这个函数就是渲染函数。
不理解?没关系,我们先看另一个示例:
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) // 等同于 const obj = {name: 'ph'} function renderFunction(){ with(this){console.log('hello: ' + name)} } renderFunction.call(obj) // hello: ph
这下理解了吧。我们将 code.render
指向的字符串导出到外界,外界利用 new Function()
创建渲染函数。
前面提到执行渲染函数会生成 vNode
。看看 code.render
就能知晓,里面出现的 _v
和 _c
,分别用于生成元素类型的 vNode 和文本类型的 vNode。请看相关源码:
// 创建文本类型的 vNode target._v = createTextVNode; function createTextVNode (val) { return new VNode(undefined, undefined, undefined, String(val)) } // 创建元素类型的 vNode 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: 关于 vue 中解析器、优化器和生成器里面具体是如何实现的,本系列就不展开了。
其他章节请看:
vue 快速入门 系列
这篇关于vue 快速入门 系列 —— 模板的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16Vue3资料:新手入门必读教程
- 2024-11-16Vue3资料:新手入门全面指南
- 2024-11-16Vue资料:新手入门完全指南
- 2024-11-16Vue项目实战:新手入门指南
- 2024-11-16React Hooks之useEffect案例详解
- 2024-11-16useRef案例详解:React中的useRef使用教程
- 2024-11-16React Hooks之useState案例详解
- 2024-11-16Vue入门指南:从零开始搭建第一个Vue项目
- 2024-11-16Vue3学习:新手入门教程与实践指南
- 2024-11-16Vue3学习:从入门到初级实战教程