Vue 2.0学习笔记:Vue的模板

编辑推荐: 掘金是一个高质量的技术社区,从 CSS 到 Vue.js,性能优化到开源类库,让你不错过伟德19463331开发的每一个技术干货。 点击链接查看最新伟德19463331内容,或到各大应用市场搜索「 掘金」下载APP,技术干货尽在掌握中。
学习Vue 2.0也有一段时间了,从前面的学习过程中,也知道在Vue中使用模板的基础知识。我们知道如何使用字符串插值在Vue中输出数据。其实在Vue的模板中,我们还可以做一些扩展,比如可以在字符串插值语法中使用简单的JavaScript表达式。之所以说简单的JavaScript表达式,是因为只能包含一个表达式,因此,不能使用循环或任何复杂的逻辑。不管怎么说,这样的逻辑不属于我们的模板,在Vue实例中放置一个方法会比较好。这我们后面会深入学习到这方面的知识点。 那么现在,我们系统的来学习一下Vue中的模板。

模板语法

Vue使用了基于HTML的模板语法,允许开发者声明式地将DOM绑定至底层Vue实例的数据。所有Vue的模板都是合法的HTML,所以能被遵循规范的浏览器和HTML解析器解析。 在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应系统,在应用状态改变时,Vue能够智能地计算出重橷渲染伟德1946手机版的最小代价并应用到DOM操作上。 如果你熟悉虚拟DOM并且偏爱JavaScript的原始力量,你也可以不用模板,直接写渲染函数,使用可选的JSX语法。 比如,我们有这样一个简单的例子。假设我们的数据对象data中有一个包含任意数字的age属性。我们可以在Vue的模板中通过{{ }}age插入进去,这个时候,在页面中就会渲染出age的属性值: <div id="app"> <h1>{{ age }}</h1> </div> let app = new Vue({ el: '#app', data () { return { age: 27 } } }) 效果如下所示: 这里我们使用了模板中最简单的文本插值方式,也是数据绑定最常见的形式。这种方法常称为双大括号(Mustache)语法。Mustache标签将会被替代为对应数据对象上age属性的值。无论何时,绑定的数据对象上age属性发生了改变,插值处的内容都会更新。 这种文本插值绑定数据虽然简单,但其也有一点的不足之处,那就是当你的页面渲染慢,或者你的JS失效时。页面会将{{ age }}这样的字符渲染出来,给你的用户造成误解,有种不友好的用户体验。 前面提到过,文本插值绑定数据的方式,只要age发生变化,那么页面渲染出来的数据就会发生变化。但也可以通过v-once指令,让文本插值只执行一次,使用了这个指令,当数据改变时,插值处的内容并不会更新。但请留心这会影响到该节点上所有的数据绑定: <div id="app"> <h1>未使用v-once指令:{{ age }}</h1> <h1 v-once>使用v-once指令:{{ age }}</h1> </div> 从上面的效果中可以看出,没有添加v-once指令的文本插值会变化,添加了的则不会再有任何变化: 不管是出于什么原因,我想把这个数字乘以2。在文本插值语法中我们可以在模板内直接添加一个简单的JavaScript表达式来实现: <div id="app"> <h1>{{ age * 2 }}</h1> </div> 这里只是将age属性乘以2,如果运行代码,我们就可以看到期望的数字输出,如上图所示。请记住,我们可以在模板中直接访问age属性,因为Vue代理我们的数据属性,因此不必显式地访问数据对象上的属性。事实上,如果你试着这样做,它就不会起作用了。 接着让我们一起看一下如何在模板中使用布尔逻辑。由于只能使用单个表达式,因此不能使用正常的if语句。然而,你可以做的是使用if的简写语法,三元表达式。假设我们想根据data中的age值进行判断,超过60输出“你老了”,反之输出“你还年轻”。那么我们可以在模板中这样做: <div id="app"> <h1>{{ age > 60 ? '你老了' : '你还年轻' }}</h1> </div> 运行上面的代码,你将看到下面这样的结果: 为了验证我们上面的说法是否正确,咱位可以在浏览器的控制台上修改age的值,比如将age的值修改成80,你将看到的效果如下: 再来看一个表达式的例子。我将在data中添加一个name的属性,并且将我的全名Airen Liao作为name的值。 let app = new Vue({ el: '#app', data () { return { age: 27, name: 'Airen Liao' } } }) dataname的值包含了我的第一个和最后一个名字,但是我只想在页面上显示我的第一个名字。我能做的就是通过split方法把name用空格分开。 <div id="app"> <h1>{{ name.split(' ') }}</h1> </div> 上面的方法只是把name中的值以空格分隔符将值以数组的形式输出,比如下图所示: 事实上并未得到我的第一名"Airen"。我们在上面的基础上,添加一个数组的索引号0,像这样: <div id="app"> <h1>{{ name.split(' ')[0] }}</h1> </div> 这样一来,得到期望得的效果: 这只是表达式的另一个例子。你可以用一个JavaScript表达式做很多事情,但要尽量保持简单。如果你需要更杂的逻辑,那么你就不应该尝试在模板中使用,比如嵌套的if语句。另外,如果你需要在模板中多次使用相同的表达式,那么最好也不要将它嵌入到模板内,而更应该选择在Vue实例中使用。可以考虑使用Vue的方法来完成,我们后面会深入的学习这方面的知识。 除了使用文本插值{{}}将数据值插入到模板之外,还可以考虑使用v-textv-html这样的指令来插入数据。有关于v-textv-html指令在Vue模板中的使用,可以阅读《Vue 2.0学习笔记:v-textv-html》一文。这里不再做过多的阐述。 当然很多时候,还想在模板中根据一定的条件进行渲染,这个时候可以考虑使用v-ifv-show这样的指令来帮助大家。另外对于列表性的渲染,使用v-for能帮我们省下不少的时间。 熟悉React的同学应该知道JSX,其实在Vue中也可以使用JSX。至于JSX是什么,不做过多阐述。
JSX就是一种对JavaScript的补充,用来描述伟德1946手机版的UI部分,类似模板语言,但它完整支持JavaScript本身的语法特性。 —— 关于JSX的介绍
JSX只是对JavaScript的补充并没有得到浏览器的支持,所以你需要用Babel搭配babel-preset-vue来获得完整的Vue JSX功能。 看一个简单的示例,如果我们使用render函数,我们一般这样写: // script.js file new Vue({ el: '#app', data: { msg: 'Show the message' }, methods: { hello () { alert('Here is the message') } }, render (createElement) { return createElement( 'span', { class: { 'my-class': true }, on: { click: this.hello } }, [ this.msg ] ); }, }); <!-- index.html file --> <div id="app"> <!--span will render here--> </div> 换成JSX之后: // script.js file new Vue({ el: '#app', data: { msg: 'Show the message.' }, methods: { hello () { alert('This is the message.') } }, render(h) { return ( <span class={{ 'my-class': true }} on-click={ this.hello } > { this.msg } </span> ) } }); <!-- index.html file --> <div id="app"> <!--span will render here--> </div> 有关于这方面的详细介绍,这里就不做过多的阐述,如果你对这方面东西感兴趣,可以阅读下面这些文章:

模板渲染

Vue 2.0的模板渲染借鉴了React的Virtual DOM。并且基于Virtual DOM,它还可以支持服务端渲染(SSR),也支持JSX语法。 在了解Vue的模板渲染方面的知识前,先上一张图: 从这张图中,我们可以初步看到一个Vue的应用是如何运行起来的,模板通过编译生成AST,再由AST生成Vue的渲染函数,渲染函数结合数据生成Virtual DOM树,对Virtual DOM进行diffpatch后生成新的UI。 我们要对一些相关的知识有所了解:
  • Vue的模板
  • AST数据结构
  • VNode数据结构
  • Virtual DOM
  • createElement函数
  • render函数
  • 观察者(Watcher)

Vue的模板

前面提到过,Vue的模板基于纯HTML,基于Vue的模板语法,我们可以比较方便地声明数据和UI的关系

AST 数据结构

AST是Abstract Syntax Tree的简写,俗称抽象语法树,是源代码的抽象语法结构的树状表现形式,计算机学科中编译原理的概念。而Vue就是将模板代码映射为AST数据结构,进行语法解析。 Vue使用了HTML Parser将HTML模板解析为AST,并且对AST进行一些优化的标记处理,提取最大的静态树,方便Virtual Dom时直接跳过diff

VNode

VNode可以理解为Vue的虚拟DOM的基类,通过new实例化的VNode大致可以分为:
  • EmptyVNode: 没有内容的注释节点
  • TextVNode: 文本节点
  • ElementVNode: 普通元素节点
  • ComponentVNode: 伟德1946手机版节点
  • CloneVNode: 克隆节点,可以是以上任意类型的节点,唯一的区别在于isCloned属性为true
一个VNode的实例对象包含了以下属性:
  • tag: 当前节点的标签名
  • data: 当前节点的数据对象
  • children: 数组类型,包含了当前节点的子节点
  • text: 当前节点的文本,一般文本节点或注释节点会有该属性
  • elm: 当前虚拟节点对应的真实的DOM节点
  • ns: 节点的namespace
  • context: 编译作用域
  • functionalContext: 函数化伟德1946手机版的作用域
  • key: 节点的key属性,用于作为节点的标识,有利于patch的优化
  • componentOptions: 创建伟德1946手机版实例时会用到的选项信息
  • child: 当前节点对应的伟德1946手机版实例
  • parent: 伟德1946手机版的占位节点
  • raw: Raw HTML
  • isStatic: 静态节点的标识
  • isRootInsert: 是否作为根节点插入,被<transition>包裹的节点,该属性的值为false
  • isComment: 当前节点是否是注释节点
  • isCloned: 当前节点是否为克隆节点
  • isOnce: 当前节点是否有v-once指令
下面是 Vue 2.0 源码中 VNode 数据结构 的定义 constructor { this.tag = tag //元素标签 this.data = data //属性 this.children = children //子元素列表 this.text = text this.elm = elm //对应的真实 DOM 元素 this.ns = undefined this.context = context this.functionalContext = undefined this.key = data && data.key this.componentOptions = componentOptions this.componentInstance = undefined this.parent = undefined this.raw = false this.isStatic = false //是否被标记为静态节点 this.isRootInsert = true this.isComment = false this.isCloned = false this.isOnce = false }

Virtual DOM

Virtual DOM树,Vue的Virtual DOM Patching算法是基于snabbdom实现的,并在此基础上作了很多的调整和改进。那么我们有真实的DOM,为什么要使用Virtual DOM。其中最大的原因就是document.createElement这个方法创建的真实DOM元素会带来性能上的损失。而VNode就是简化版的真实DOM元素,关联着真实的DOM,比如属性elm,只包括我们需要的属性,并新增了一些在diff过程中需要使用的属性,例如isStatic

createElement函数

createElement函数也经常被叫做h函数,它被用来创建一个VNode(虚拟DOM节点)。可以通过this.$createElement访问它但同时它也是render函数的第一个参数。

render函数

这个函数是通过编译模板文件得到的,其运行结果是VNode。render函数与JSX类似,Vue 2.0中除了Template也支持JSX的写法。大家可以使用Vue.compile(template)方法编译下面这段模板。 <div id="app"> <header> <h1>I am a template!</h1> </header> <p v-if="message"> {{ message }} </p> <p v-else> No message. </p> </div> 方法会返回一个对象,对象中有 renderstaticRenderFns 两个值。看一下生成的 render函数: (function() { with(this){ return _c( // 创建一个 div 元素 'div', { attrs:{"id":"app"} //div 添加属性 id }, [ // 静态节点 header,此处对应 staticRenderFns 数组索引为 0 的 render 函数 _m(0), // 空的文本节点 _v(" "), // 三元表达式,判断 message 是否存在 // 如果存在,创建 p 元素,元素里面有文本,值为 toString(message) // 如果不存在,创建 p 元素,元素里面有文本,值为 No message. (message) ? _c('p',[_v("\n "+_s(message)+"\n ")]) : _c('p',[_v("\n No message.\n ")]) ] ) } }) 要看懂上面的 render函数,只需要了解 _c_m_v_s 这几个函数的定义,其中:
  • _ccreateElement(创建元素)
  • _mrenderStatic(渲染静态节点)
  • _vcreateTextVNode(创建文本DOM)
  • _stoString (转换为字符串)
除了 render 函数,还有一个 staticRenderFns 数组,这个数组中的函数与 VDOM 中的 diff 算法优化相关,我们会在编译阶段给后面不会发生变化的 VNode 节点打上 statictrue 的标签,那些被标记为静态节点的 VNode 就会单独生成 staticRenderFns 函数: // 上面 render 函数 中的 _m(0) 会调用这个方法 (function() { with(this){ return _c('header',[_c('h1',[_v("I'm a template!")])]) } }) 其实render函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中, Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。

观察者 (Watcher)

每个Vue伟德1946手机版都有一个对应的Watcher,这个Watcher将会在伟德1946手机版render的时候收集伟德1946手机版所依赖的数据,并在依赖有更新的时候,触发伟德1946手机版重新渲染。我们根本不需要写shouldComponentUpdate,Vue就会自动优化并更新需要更新的UI 上图中,咱们可以以render函数作为一道分割线,render函数左边可以称之为编译期,将Vue的模板转换为渲染函数。render函数的右边是Vue的运行时,主要是基于渲染函数生成Virtual DOM树,然后对Virtual Dom树进行diffpatch 接下来再上一张Vue模板渲染过程的图: Vue模板的渲染主要经历以下几个过程:
  • new Vue():实例化Vue
  • $mount(): 获取模板,并且在这过程中通过调用相关方法_countnew Watcher()实现数据响应式,当Watcher监听到数据变化,就会执行render函数输出一个新的 VNode 树形结构的数据(VNode对象即Virtual DOM)
  • compileToFunction(): 将 template 编译成 render 函数。首先读缓存(在compileToFunction()中,会创建一个对象,把complice编译完后的对象的renderstaticRenderFns 两个属性分别转换成函数缓存在对象中,然后把对象存进缓存,没有缓存就调用 compile 方法拿到 render 函数的字符串形式,在通过new Function 的方式生成真正的渲染函数
  • compile:将 template 编译成 render 函数的字符串形式,这个函数主要有三个步骤组成:parseoptimizegenerate,最终输出一个包含 astrenderstaticRenderFns的对象。compile 函数主要是将 template 转换为 AST,优化 AST,再将 AST 转换为 render函数字符串,render 函数与数据通过 Watcher 产生关联。
  • update()update判断是否首次渲染,是则直接创建真实DOM,否则调用patch(),并且进行触发钩子和更新引用等其他操作
  • patch():新旧 VNode 对比的 diff 函数,对两个树结构进行完整的diffpatch的过程,最终只有发生了变化的节点才会被更新到真实 DOM 树上。
  • destroy():完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。触发 beforeDestroydestroyed 的钩子。在大多数场景中你不应该调用这个方法。最好使用 v-ifv-for 指令以数据驱动的方式控制子伟德1946手机版的生命周期。
有关于Vue模板的渲染,我也是看得云里来,雾里去。不过不要紧,对于初学者,咱们能整明白怎么使用Vue的模板,就行了。如果你想深入了解底层的渲染原理,可以阅读下面几篇文章:

总结

这篇文章从Vue模板使用入手,了解怎么在Vue中使用模板,然后一起学习了Vue模板渲染的相关知识。如果你和我一样是Vue的初学者,不必太过纠结是否能整明白模板的渲染的原理,我们首要的条件就是学会怎么在Vue中使用模板,得到我们想要的Web运用。 如果你在这方面有更多的经验,欢迎在下面的评论中与我们一起分享,如果文章有不对之处,还请各路大婶拍正。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等伟德19463331脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。
如需转载,烦请注明出处:https://www.w3cplus.com/vue/vue-template.html
返回顶部