3.Vue模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。
3.1 插值
3.1.1 文本
双大括号
{{}}
语法:`<span>Message: {{msg}}</span>`
运行时会将括号内的内容实时替换为数据对象的
msg
property的值。v-once
指令:<span v-once>这个将不会改变: {{ msg }}</span>
使用v-once指令意味着只能执行一次插值,此后数据改变时插值处的内容不会更新。
3.1.2 输出被解析的HTML
双大括号会将数据解释为普通文本而非HTML代码,为了输出会被解析的 HTML,需要使用 v-html
指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
这个
span
的内容将会被替换成为 property 值rawHtml
。
注意,不能企图使用 v-html
来复合局部模板,因为 Vue 不是基于字符串的模板引擎。相应地,你应该使用Vue组件。
3.1.3 attribute
双大括号语法不能作用在 HTML 的属性(attribute)上,此时应该使用 v-bind
指令:
<div v-bind:id="dynamicId"></div>
值得注意的是,对于布尔属性(即只要它们存在就意味着值为 true
),v-bind
的机制略有不同:
<button v-bind:disabled="isButtonDisabled">Button</button>
如果 isButtonDisabled
的值是 null
、undefined
或 false
,则 disabled
attribute 甚至不会被包含在渲染出来的 <button>
元素中。
3.1.4 使用js表达式
实际上对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。
不过这里有个限制:每个绑定都只能包含单个表达式。所以下面的例子都不会生效:
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
需要强调的是,模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math
和 Date
,而不应该在模板表达式中试图访问用户定义的全局变量。
3.2 指令
指令 (Directives) 是带有 v-
前缀的、vue模板中html元素的特殊 attribute。一般情况下,指令是单个 JavaScript 表达式(v-for除外),如这里的v-if
指令将根据表达式 seen
的值的真假来插入/移除 <p>
元素:
<p v-if="seen">现在你看到我了</p>
3.2.1 指令的参数
有的指令能够接收一个参数,在指令后以冒号表示,如v-bind
可以用于响应式地更新HTML的attribute:
<a v-bind:href="url">...</a>
在这里
href
是参数,告知v-bind
指令将该元素的href
attribute 与表达式url
的值绑定。
3.2.2 指令的动态参数
从 Vue 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<a v-bind:[attributeName]="url"> ... </a>
这里的
attributeName
会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例的数据对象有一个 property 叫做
attributeName
,当其值为"href"
时上述绑定将等价于v-bind:href
。
动态参数正常情况下会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
由于某些字符(如空格、引号)放在 HTML 元素的 attribute 名里是无效的,所以动态参数表达式有一些语法约束:
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
此外,在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
3.2.3 修饰符
饰符符是以 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
<form v-on:submit.prevent="onSubmit">...</form>
3.2.4 两个常用指令缩写
v-bind
:缩写为冒号:
<!-- 完整语法 --> <a v-bind:href="url">...</a> <!-- 缩写 --> <a :href="url">...</a>
v-on
:缩写为@
<!-- 完整语法 --> <a v-on:click="doSomething">...</a> <!-- 缩写 --> <a @click="doSomething">...</a>
3.3 Class与Style绑定
操作元素的 class 列表和内联样式是数据绑定的一个常见需求,由于它们都是 attribute,因而可以用 v-bind
进行处理:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错,故而在将 v-bind
用于 class
和 style
时,Vue.js 做了专门的增强——表达式结果的类型除了字符串之外,还可以是对象或数组。
3.4 条件渲染
3.4.1 v-if
v-if
指令用于条件性地渲染一块内容这,这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>
也可以用 v-else
添加一个“else 块”:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
v-else
元素必须紧跟在带v-if
或者v-else-if
的元素的后面,否则它将不会被识别。
也有v-else-if
,顾名思义即可:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
类似于
v-else
,v-else-if
也必须紧跟在带v-if
或者v-else-if
的元素之后。
可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
,此时最终的渲染结果将不包含 <template>
元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
3.4.2 v-show
另一个用于根据条件展示元素的选项是 v-show
指令,其用法别无二致:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中——v-show
只是简单地切换元素的 CSS 属性 display
。
注意,
v-show
不支持<template>
元素,也不支持v-else
。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
3.4.3 key
Vue 被设定为尽可能高效地渲染元素,因而通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的 placeholder
。
但是这样也不总是符合实际需求,所以 Vue 还提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”这种理念,只需添加一个具有唯一值的 key
attribute 即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
现在,每次切换时,输入框都将被重新渲染。但注意
<label>
元素仍然会被高效地复用,因为它们没有添加key
attribute。
3.5 列表渲染
可以用 v-for
指令基于一个数组来渲染一个列表。
3.5.1 常用用法
v-for
指令通常使用 item in items
形式的特殊语法,其中 items
是数据源数组,而 item
则是代表当前被迭代到的数组元素。
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
3.5.2 遍历时携带索引
v-for
还支持一个可选的第二个参数,即当前项的索引。
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Tips:也可以用 of
替代 in
作为分隔符,这样设计只是因为它更接近 JavaScript 迭代器的语法:
<div v-for="item of items"></div>
3.5.3 使用v-for
遍历对象的property
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
你也可以提供第二个的参数为 property 名称 (也就是键名):
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
还可以用第三个参数作为索引:
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
3.5.4 key attribue(TMD说了些啥?)
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"
。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
attribute:
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,key
并不仅与 v-for
特别关联。后面我们将在指南中看到,它还具有其它用途。
不要使用对象或数组之类的非基本类型值作为 v-for
的 key
。请用字符串或数值类型的值。
更多 key
attribute 的细节用法请移步至 key
的 API 文档。
3.5.5 v-for
中也可以调用方法
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
3.5.6 v-for
循环固定的次数
v-for
也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
3.5.7 v-for
与 v-if
一起使用时
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。
当你只想为部分项渲染节点时,这种优先级的机制会十分有用,下面的代码将只渲染未完成的 todo:
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if
置于外层元素 (或template
) 上:
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
3.6 事件处理——v-on
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码:
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
当在内联语句中需要访问原始的 DOM 事件时,可以用特殊变量
$event
把它传入方法:<button v-on:click="warn('Form cannot be submitted yet.', $event)"> Submit </button>
// ... methods: { warn: function (message, event) { // 现在我们可以访问原生事件对象 if (event) { event.preventDefault() } alert(message) } }
当事件处理逻辑比较复杂时,v-on
还可以接收一个需要调用的方法名称:
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 此时也可以用 JavaScript 直接调用该方法
example2.greet() // => 'Hello Vue.js!'
事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求,尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
- ...
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
注意,使用修饰符时,顺序很重要,相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击。
3.7 表单输入绑定——v-model
可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定,它会根据控件类型自动选取正确的方法来更新元素。v-model
本质上其实是语法糖——通过监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的 value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
对于需要使用输入法(如中文、日文、韩文等) 的语言,你会发现
v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用input
事件。
示例:
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
在文本区域插值 (
<textarea>{{text}}</textarea>
) 并不会生效,需要用v-model
来代替。
v-model
修饰符:
.lazy
:默认情况下,v-model
在每次input
事件触发后将输入框的值与数据进行同步,但可以添加lazy
修饰符转为在change
事件_之后_进行同步。.number
:如果想自动将用户的输入值转为数值类型,可以给v-model
添加number
修饰符:<input v-model.number="age" type="number">
.trim
:自动过滤用户输入的首尾空白字符。
3.8 自定义指令
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
举个聚焦输入框的例子:当页面加载时,该元素将获得焦点。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。我们可以用指令来实现这个功能:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
如果想注册局部指令,组件中也接受一个 directives
的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后就可以在模板中任何元素上使用新的 v-focus
属性了,如比如:
<input v-focus>
指令对象的钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
指令钩子函数会被传入以下参数:
除了
el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
以下为一个自定义钩子的示例:
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
指令的参数也可以是动态的。
3.9 模板编译
Vue 的模板实际上被编译成了渲染函数,这是一个实现细节,通常不需要关心。但如果你想看看模板的功能具体是怎样被编译的,可能会发现非常有意思......