1. 在 HTML 网页中使用 vue3 的3个基本步骤
a.通过 script
标签的 src
属性,在当前网页中全局引入 vue3 的脚本文件:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
b.创建 vue3 的单页面应用程序实例:
// 2.1 从 Vue 对象中解构出 createApp 函数
const { createApp } = Vue
// 2.2 调用 createApp 这个函数,就能够创建出一个单页面应用程序的实例
const app = createApp()
// 2.3 调用 app 实例对象上的 mount() 函数,
// 指定单页面应用程序 app,实际要控制页面上哪个区域的渲染
app.mount('#app')
c.声明 vue3 的单页面应用程序实例,实际要控制的页面区域:
<!-- 注意:如果内容为空,则 vue3 会在提示一个警告消息:
[Vue warn]: Component is missing template or render function. at <App> -->
<div id="app"></div>
2. 定义和渲染数据
a.在调用 createApp()
函数时,可以提供一个对象作为配置参数
,例如:
const app = createApp({ /*配置对象*/ })
b.如果想提供要渲染的数据,可以在步骤1的配置对象中,通过 data
节点提供渲染期间要使用的数据:
const app = createApp({
// 2.1 注意:data 节点是一个函数
data() {
// 2.2 在 data 函数内部,return 的这个对象,就是数据对象,
// 要渲染的数据,可以直接写到这个对象中,例如 return { name: 'ls' }
return {}
}
})
c.在步骤2的 data 节点中,定义一个名为 name
的数据,值是 zhangsan:
const app = createApp({
data() {
return {
name: 'zhangsan'
}
}
})
d.在 vue3 控制的模板结构中,使用 {{ 数据名 }}
语法,把数据渲染出来:
<div id="app">
<h1>我是:{{ name }}</h1>
</div>
e.拓展:当我们修改 data 节点下的数据后,即可看到页面上的 HTML 内容会自动被刷新。这就是 vue 的强大之处:数据驱动视图。修改 data 数据的示例代码如下:
app._instance.proxy.name = 'wangwu'
3. vue3 中常用的渲染指令
在 vue 中,指令是带有 v-
前缀的特殊 attribute,它是 vue 提供的特殊语法,大家有必要掌握 vue 中常用指令的使用。
指令能够辅助前端程序员高效地把数据渲染为 HTML 的结构,而程序员不需要调用任何操作 DOM 的 API。
3.1常用指令的分类
1.内容渲染指令
a. 插值表达式
插值表达式(又叫做:Mustache)的语法为 {{ }}
,vue 在解析模板期间,会把 {{ }}
所在的位置,替换为对应的数据值,例如:
<h1>大家好,我是:{{ name }}</h1>
vue 会把 name
的值,替换到 {{ name }}
所在的位置。
注意:插值表达式 {{ }} 是唯一一个不以 v- 前缀开头的指令。
b.v-text
v-text
指令用来填充 HTML 元素的内容,如果 HTML 元素内部有其它内容存在,则会被覆盖掉。语法格式如下:
<h3 v-text="msg">展示看看</h3>
对应的数据为:
const app = createApp({
data() {
return {
msg: '我是绑定的数据'
}
}
})
注意:由于 v-text 指令存在覆盖已有内容的问题,所以在实际开发中它很少被用到。最常用的还是 {{ }} 插值表达式,因为它只是占位符,不会覆盖已有内容。
c.v-html
v-html
指令用来渲染带有 HTML 标记的文本内容,它可以把 HTML 标记解析为真正的 HTML 元素,并插入到模板中渲染。
而插值表达式和 v-text 指令只会把 HTML 标记渲染为纯文本,而不是 HTML。
v-html 的语法格式如下:
<div v-html="rawHtml"></div>
对应的数据为:
const app = createApp({
data() {
return {
rawHtml: '<span style="color: red;">少年强中国说</span>'
}
}
})
2. 属性绑定指令
a.v-bind
v-bind
指令用来为元素的属性绑定动态的属性值。指令语法如下:
<div v-bind:title="titleMsg">xxx</div>
对应的数据为:
const app = createApp({
data() {
return {
titleMsg: '哇哈哈'
}
}
})
又例如,为图片的 src 属性动态绑定属性的值:
<img v-bind:src="url" />
对应的数据为:
const app = createApp({
data() {
return {
url: 'https://img.yzcdn.cn/vant/cat.jpeg'
}
}
})
b.v-bind的简写
在实际开发中,v-bind
指令的使用频率非常高,为了简化它的写法,vue 规定 v-bind
指令可以简写为英文的 :
且二者是完全等价的。如上面的例子可以使用 :
简写为:
<div :title="titleMsg">xxx</div>
<img :src="url"
对应的数据为:
const app = createApp({
data() {
return {
titleMsg: '哇哈哈',
url: 'https://img.yzcdn.cn/vant/cat.jpeg'
}
}
})
注意:今后在 vue 项目开发中,只要看到某个属性前面出现了英文的 : 那么,一定是为这个属性绑定了动态的值。
c.绑定布尔值
在 vue 中,某些属性的取值可以是布尔值 true 或 false,表示当前的属性是否应该应用于当前的元素。例如 disabled
属性:
<!-- 禁用按钮A -->
<button :disabled="true">按钮A</button>
<!-- 不禁用按钮B -->
<button :disabled="false">按钮B</button>
与之类似的,还有 radio 和 checkbox 的 checked
属性:
<!-- 默认选中“男” -->
<input type="radio" name="gender" :checked="true">男
<input type="radio" name="gender">女
<!-- 默认选中“足球”和“乒乓球” -->
<input type="checkbox" name="hobby">篮球
<input type="checkbox" name="hobby" :checked="true">足球
<input type="checkbox" name="hobby" :checked="true">乒乓球
另外,表单元素 select
下的 option
选项的 selected
属性,也可以绑定布尔值:
<select>
<option value="北京">北京</option>
<option value="上海" :selected="true">上海</option>
<option value="广州">广州</option>
</select>
d.动态绑定多个值
如果要为某个元素同时绑定多个动态的属性值,可以把多个动态属性封装为一个 JavaScript 对象
const app = createApp({
data() {
return {
// propObj 对象中封装了一系列属性的键值对
attrsObj: {
id: 'box',
class: 'container',
title: '布局容器'
}
}
}
})
通过不带参数的 v-bind
指令,即可方便的把 attrsObj 对象中封装的属性,一次性绑定到对应的元素上:
<div v-bind="attrsObj">顶部 header 区域</div>
注意:不带参数的 v-bind 指令,指的是省略了 :属性名
的用法。
e. 拓展:使用 JavaScript 表达式
在 vue 的数据绑定中,除了支持简单的属性名绑定之外,还支持完整的 JavaScript 表达式绑定。
例如,以下这些都属于简单的属性名绑定,它们是直接把 data 中数据项的名字,绑定到了模板中:
<div>我是:{{ name }}</div>
<div v-text="msg"></div>
<img :src="url" />
除此之外,还支持表达式的绑定,例如:
<!-- 函数的调用 & 数学运算 -->
<div>我是:{{ name.toUpperCase() }},我今年{{ age + 1 }}岁了。</div>
<!-- 函数的调用 -->
<div v-text="msg.split('').reverse().join('')"></div>
<!-- 字符串的拼接 -->
<img :src="'https://img.yzcdn.cn/vant/' + url" />
<!-- 三元表达式 -->
<div>{{ age >= 18 ? '抽烟喝酒烫头' : '可乐牛奶娃哈哈' }}</div>
对应的数据如下:
const app = createApp({
data() {
return {
name: 'liulongbin',
age: 17,
msg: '冯绍峰',
url: 'cat.jpeg'
}
}
})
3.双向绑定指令
v-model
双向绑定指令,简化了表单元素的赋值和取值操作。
v-model
的作用:
1.data 数据源发生变化,自动重新渲染页面
2.表单数据发生变化,自动更新到 data 数据源中
a.文本框的双向绑定
input 元素通过 v-model 指令,可以方便地进行赋值和取值,示例代码如下:
<p>Message 的值是:{{ message }}</p>
<input type="text" v-model="message">
对应的数据如下:
const app = createApp({
data() {
return {
message: 'hello'
}
}
})
b.多行文本框的双向绑定
textarea 元素通过 v-model 指令,可以方便地进行赋值和取值,示例代码如下:
<p>Message 的值是:</p>
<pre>{{ message }}</pre>
<textarea v-model="message"></textarea>
对应的数据如下:
const app = createApp({
data() {
return {
// 注意:这里的 \n 是换行符
message: 'hello \nworld.'
}
}
})
c.复选框的双向绑定
单一复选框的双向绑定,绑定的是布尔类型的值:
<p>复选框选中的flag值为:{{flag}}</p>
<input type="checkbox" v-model="flag">
const app = createApp({
data() {
return {
// 是否被选中
flag: false
}
}
})
多个复选框的双向绑定,绑定的是数组类型的值,而且每个 checkbox 必须通过 value 属性提供选中项的值:
<p>多个复选框选中的 hobbies 值为:{{ hobbies }}</p>
<label><input type="checkbox" v-model="hobbies" value="篮球">篮球</label>
<label><input type="checkbox" v-model="hobbies" value="足球">足球</label>
<label><input type="checkbox" v-model="hobbies" value="冰球">冰球</label>
const app = createApp({
data() {
return {
// 选中的值
hobbies: []
}
}
})
d.单选按钮的双向绑定
单选按钮的特点是多选一,所以对单选按钮进行双向绑定时,需要把多个单选按钮通过 v-model 指令绑定到同一个数据源,并通过 value 属性指定选中后的值:
<p>单选按钮选中的 gender 值为:{{ gender }}</p>
<label><input type="radio" v-model="gender" value="男">男</label>
<label><input type="radio" v-model="gender" value="女">女</label>
数据如下:
const app = createApp({
data() {
return {
// 选中的值
gender: '男'
}
}
})
e.选择器的双向绑定
单选选择器的双向绑定,只允许选中一个值:
<p>选中的城市为:{{ city }}</p>
<select v-model="city">
<option value="">请选择</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="nanjing">南京</option>
</select>
const app = createApp({
data() {
return {
city: ''
}
}
})
多选选择器的双向绑定,允许选中多个值,所以需要绑定数组格式的数据源:
<p>选中的城市为:{{ areas }}</p>
<select v-model="areas" multiple>
<option value="shunyi">顺义区</option>
<option value="haidian">海淀区</option>
<option value="daxing">大兴区</option>
</select>
const app = createApp({
data() {
return {
areas: []
}
}
})
f.v-model 的 .lazy 修饰符
默认情况下,v-model
会在每次 input
事件后更新数据。可以添加 .lazy
修饰符来改为在每次 change
事件后更新数据:
<input v-model.lazy="msg" />
g. v-model 的 .number 修饰符
如果你想让用户输入自动转换为数字,你可以在 v-model
后添加 .number
修饰符来管理输入:
<input v-model.number="age" />
注意:
- 如果该值无法被
parseFloat()
处理,那么将返回原始值。 -
number
修饰符会在输入框有type="number"
时自动启用。
h.v-model 的 .trim 修饰符
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model
后添加 .trim
修饰符:
<input v-model.trim="msg" />
4.条件渲染指令
条件渲染指令用来条件性地渲染页面上的某一部分内容。只有表达式的条件成立,才会真正渲染这一部分的内容。
常用的条件渲染指令是 v-if
、v-else
和 v-else-if
。其中,v-if
指令可以单独使用,也可以结合 v-else
和 v-else-if
指令实现两个或多个条件的按需渲染。
1.v-if的使用
v-if 的语法格式如下:
<div v-if="表达式"></div>
其中,只有表达式的返回值为 true 时,才会真正渲染被 v-if 指令控制的 div 元素。
如果 v-if 的表达式返回值为 false,则被 v-if 指令控制的 div 不会被渲染到浏览器中。
例如:
<div v-if="flag">无敌是多么的寂寞</div>
const app = createApp({
data() {
return {
flag: true
}
}
})
2. v-if 结合 v-else 的使用
v-if
指令可以结合 v-else
指令一起使用。
当条件为真时渲染被 v-if
指令控制的元素,当条件为假时渲染被 v-else
指令控制的元素。例如:
<div v-if="age >= 18">抽烟喝酒烫头</div>
<div v-else>牛奶可乐娃哈哈</div>
注意:v-else 指令不需要通过 = 指定相应的表达式,因为 v-else 是兜底的条件,只要前面的所有条件都不满足,那么必然会触发 v-else 的执行。
3.v-if 结合 v-else-if 和 v-else 的使用
v-if
指令可以结合 v-else-if
和 v-else
指令一起使用,从而组成复杂的条件渲染逻辑。
当 v-if
或某个 v-else-if
相应的条件为真时,被控制的元素才会被渲染。
最后的 v-else
依然是兜底的条件,当所有的 v-if
和 v-else-if
条件都不成立时,才会触发 v-else 的执行。例如:
<div v-if="score === 'A'">优秀</div>
<div v-else-if="score === 'B'">良好</div>
<div v-else-if="score === 'C'">一般</div>
<div v-else>差</div>
const app = createApp({
data() {
return {
score: 'A'
}
}
})
4. <template> 上的 v-if
正常情况下 v-if
指令只能控制单个元素的显示和隐藏。如果需要使用 v-if
控制一组元素的显示和隐藏,就需要在这一组元素之外包裹一个 div 作为容器,并将 v-if
指令应用于 div 容器之上,例如:
<div v-if="true">
<h1>咏鹅</h1>
<p>鹅鹅鹅,曲项向天歌。</p>
<p>白毛浮绿水,红掌拨清波。</p>
</div>
这么做虽然能实现需求,但会在页面上渲染出一个多余的 div 容器。
更好的方案是使用 vue 内置的 <template>
元素作为外层包裹性质的容器,因为它不会被渲染为实际的元素,只起到包裹性质的作用。例如:
<template v-if="true">
<h1>咏鹅</h1>
<p>鹅鹅鹅,曲项向天歌。</p>
<p>白毛浮绿水,红掌拨清波。</p>
</template>
5.v-show 指令的使用
另一个可以用来实现条件渲染的指令是 v-show
。它的语法格式如下:
<h1 v-show="flag">Hello!</h1>
如果表达式的值为 true,则被控制的元素会被显示;
如果表达式的值为 false,则被控制的元素会被隐藏。
注意:v-show 指令不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
6.v-if 和 v-show 的对比
相同点:
v-if
和 v-show
指令都能控制元素的条件渲染。
1.如果表达式的值为 true,则被控制的元素会被显示;
2.如果表达式的值为 false,则被控制的元素会被隐藏。
不同点:
a.控制元素显示和隐藏的手段不同
-
v-if
指令会动态创建和删除被控制的元素,从而达到切换元素显示和隐藏的目的; -
v-show
指令仅切换了被控制元素上名为display
的 CSS 属性,从而达到切换元素显示和隐藏的目的;
b.初始渲染的性能不同:
- 如果初始渲染时,表达式的值为 false,则 v-if 的性能更好。
- 如果初始渲染时,表达式的值为 true,则二者性能相近。
c.频繁切换时的性能不同
- 如果需要频繁切换元素的显示和隐藏,则 v-show 的性能更好。
- 如果不需要频繁切换元素的显示和隐藏,则可以忽略二者的性能差别。
d.总结
-
v-if
有更高的切换开销 -
v-show
有更高的初始渲染开销
5.事件绑定指令
a.事件绑定的基本语法
为了响应用户对 DOM 元素的操作,vue 提供了事件绑定指令 v-on
(简写为 @
)。
当监听到 DOM 事件的触发时,会执行对应的 JavaScript 逻辑。它的语法格式为 v-on:事件名="handler"
或 @事件名="handler"
。例如:
<!-- v-on 是事件绑定指令 -->
<button v-on:click="show">按钮</button>
<!-- @ 是 v-on 指令的简写形式 -->
<button @click="show">按钮</button>
上述代码演示了如何为 button 按钮绑定 click
点击事件。
除此之外,vue 还支持绑定其它类型的事件,这里就不再一一例举了。因为把 DOM 原生事件前面的 on
替换成 v-on:
或 @
就变成了 vue 的事件绑定形式,例如:
1.onclick --> @click
2.oninput --> @input
3.onchange --> @change
b.方法事件处理器
方法事件处理器指的是:指定一个方法作为事件的处理器。例如下面的代码所示,指定了一个 show
方法作为 click
事件的处理器:
<button @click="show">按钮</button>
show
方法作为事件处理器,需要定义在 methods
节点下,例如:
const app = createApp({
data() {
return {}
},
methods: {
show(event) {
console.log('ok')
console.log(event.target.tagName)
}
}
})
在方法事件处理器的参数列表中,第一个形参 event
是事件对象。
c.基于方法事件处理器实现数值自增
声明模板结构如下:
<p>count的值为:{{ count }}</p>
<button @click="add">+1</button>
在 data 中声明数据源 count
,在 methods 中声明事件处理器 add
,代码如下:
const app = createApp({
data() {
return {
count: 0
}
},
methods: {
add() {
app._instance.proxy.count++
}
}
})
注意:methods 节点下的方法中,this 指向的就是 app._instance.proxy。所以上述代码完全可以替换为 this.count++
d.内联事件处理器
内联事件处理器相当于原生 DOM 中的内联 JavaScript,例如数值自增的操作,可以简写成内联事件处理器的形式:
<p>count的值为:{{ count }}</p>
<button @click="count++">+1</button>
const app = createApp({
data() {
return {
count: 0
}
}
})
注意:内联事件处理器通常用于简单的业务场景,如果涉及到复杂的业务逻辑,请使用方法事件处理器或在内联处理器中调用方法。
e. 在内联处理器中调用方法
首先,我们要能够明确的区分开方法事件处理器和内联事件处理器。
如果事件绑定的处理器是个纯粹的方法名,则是方法事件处理器,例如:
<button @click="show">按钮A</button>
除此之外,其它绑定事件处理器的形式,都是内联事件处理器,例如:
<!-- 绑定内联的 JavaScript -->
<button @click="count++">按钮C</button>
<!-- 绑定了一个方法的调用 -->
<button @click="show()">按钮B</button>
<!-- 绑定了方法的调用的同时,传递参数 -->
<button @click="show('Hello world.')">按钮B</button>
内联事件处理器的优点:解锁了模板向处理器方法传递参数的能力。
f.在内联事件处理器中访问事件对象
内联事件处理器的缺点:事件对象丢失了,无法在处理器方法中访问到事件对象 event。
上述问题的解决方案有两个,分别是:
- 使用特殊的 $event 变量
- 使用内联箭头函数接收并传递 event 对象
解决方案1:使用特殊的 $event 变量
<button @click="showMsg('hello world.', $event)">按钮</button>
对应的 showMsg
处理器为:
const app = createApp({
methods: {
showMsg(msg, event) {
// 改变按钮显示的文本
event.target.innerHTML = msg
// 改变按钮的背景颜色
event.target.style.backgroundColor = 'cyan'
}
}
})
解决方案2:使用内联箭头函数接收并传递 event 对象
<button @click="(event) => showMsg('你好,世界。', event)">按钮</button>
对应的 showMsg
处理器为:
const app = createApp({
methods: {
showMsg(msg, event) {
// 改变按钮显示的文本
event.target.innerHTML = msg
// 改变按钮的背景颜色
event.target.style.backgroundColor = 'cyan'
}
}
})
g. 事件修饰符
在原生 DOM 的事件处理函数中,如果想要阻止冒泡行为,则需要调用 event.stopPropagation()
;如果想要阻止默认行为,则需要调用 event.preventDefault()
。为了提高用户的开发体验,vue 提供了更优雅的方式来阻止事件冒泡或默认行为,即:事件修饰符。
在 vue 中最常用的两个事件修饰符分别是:
- .prevent
- .stop
其中 .prevnet
用来阻止默认行为,例如:
<!-- 使用 .prevent 修饰了 a 链接的 click 事件 -->
<!-- 点击超链接后,会阻止超链接的默认跳转行为 -->
<a href="https://www.baidu.com/" @click.prevent="showMsg">超链接</a>
另外 .stop
用来阻止事件冒泡,例如:
<div @click="outerHandler">
<!-- 点击内部的 button 按钮,click 事件不会向外冒泡 -->
<!-- 所以外层的 outerHandler 处理器不会执行 -->
<button @click.stop="innerHandler">按钮</button>
</div>
拓展:其它事件修饰符还有 .self、.capture、.once、.passive。具体用法请参考 vue3 官方文档 - 事件修饰符。
h.按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键,从而执行特定的操作。例如:
- 用户在输入框中按下了 enter 键,则触发提交的函数
- 用户在输入框中按下了 esc 键,则清空文本框的内容
示例代码如下:
<input type="text" v-model="msg" @keyup.enter="submit" @keyup.esc="clear">
对应的 JS 处理逻辑为:
const app = createApp({
data() {
return {
msg: '' // 文本框的数据
}
},
methods: {
// 该处理函数仅在用户按下 enter 键时触发
submit() {
console.log('提交的数据为:' + this.msg)
},
// 该处理函数仅在用户按下 esc 键时触发
clear() {
this.msg = ''
}
}
})
i.按键别名与按键名的获取
vue 为常用的按键提供了官方内置的按键别名,列表如下:
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
如果上述列表中没有你想监听的按键,则可以使用 $event.key
先获取按键的名称,再把获取到的按键名称转为 kebab-case 形式,最后利用转换得到的按键名进行监听即可,例如下面的代码监听了 CapsLock 按键:
<p>输入状态:{{ isUpperCase ? '大写' : '小写' }}</p>
<input type="text" v-model="msg" @keyup.caps-lock="changeMode">
对应的 JS 逻辑为:
const app = createApp({
data() {
return {
msg: '', // 文本框的数据
isUpperCase: false // 是否为大写输入模式
}
},
methods: {
// 仅当用户按下的是 CapsLock 键,才触发此函数的执行
changeMode() {
this.isUpperCase = !this.isUpperCase
}
}
})
j.系统按键修饰符
如果在触发事件的时候,想要判断用户是否同时按下了 Ctrl、Alt 等系统按键。此时可以使用 vue 内置的系统按键修饰符,主要有以下4个:
- .ctrl
- .alt
- .shift
- .meta [注意:meta 在 Windows 键盘上指的是 Windows 键(⊞),在 Mac 键盘上指的是 Command 键(⌘)]
例如,下面的代码监听了触发 div 的 click 事件时,是否同时按下了特定的系统按键,从而改变 div 的形状和外观:
<!-- 点击 div 的时候, -->
<!-- 1. 如果同时按下了 Ctrl 键,则添加 square 类样式 -->
<!-- 2. 如果同时按下了 Alt 键,则添加 round 类样式 -->
<!-- 3. 如果同时按下了 Shift 键,则还原为默认的 box 类样式 -->
<div class="box" :class="shape"
@click.ctrl="changeShape('square')"
@click.alt="changeShape('round')"
@click.shift="changeShape('')">
</div>
对应的 JS 逻辑为:
const app = createApp({
data() {
return {
// 类样式的名称
shape: ''
}
},
methods: {
// 事件的处理函数
changeShape(shape) {
this.shape = shape
}
}
})
对应的 CSS 样式为:
<style>
.box {
width: 300px;
height: 300px;
background-color: #efefef;
transition: all 1s ease;
}
.square {
border-radius: 20px;
background-color: cyan;
transition: all 1s ease;
}
.round {
border-radius: 50%;
background-color: lightgreen;
transition: all 1s ease;
}
</style>
k. .exact
修饰符
上述的例子中,存在一个很明显的 Bug:
- 我们希望在触发 div 的 click 事件时,仅当用户按下了
Ctrl
或Alt
或Shift
按键时,才触发changeShape
函数 - 现在的 Bug 是,用户在触发 click 事件时,如果按下了
Ctrl + Alt
的组合按键,也会触发changeShape
函数
而 .exact
修饰符可以完美的解决这个问题。.exact
修饰符表示精确匹配系统按键。
因此,我们可以针对上述的例子进行修改,在特定的系统按键修饰符的后面应用 .exact
修饰符,表示精确匹配系统按键:
<div class="box" :class="shape"
@click.ctrl.exact="changeShape('square')"
@click.alt.exact="changeShape('round')"
@click.shift.exact="changeShape('')">
</div>
注意:所谓的精确匹配系统按键,仅对系统按键修饰符生效,如果用户按下了 Ctrl + A 的组合键,也会触发 @click.ctrl.exact
所绑定的事件处理器。
k.鼠标按键修饰符
vue 还提供了鼠标按键修饰符,用来监听事件是否由特定的鼠标按键触发:
- .left
- .right
- .middle
例如,下面的代码演示了如何阻止在 h1
元素上显示鼠标的右键菜单:
<h1 @click.right.prevent>这是一个标题</h1>
注意:绑定事件时,不一定非要提供事件的处理器,我们也可只提供事件修饰符,从而达到特定的目的。
6. 列表渲染指令
v-for
指令是 vue 提供的列表渲染指令。
如果您有一个数组,想把数组中的每一项渲染为格式相似的 HTML 结构,那么 v-for 指令可以帮助您实现列表数据的渲染。
使用场景:商品列表、用户列表等。
a. v-for 的基本使用
v-for 的基本语法格式为:
v-for="当前循环项 in 数组"
其中关键字 in
前面的是当前循环项,关键字 in
后面的要循环的数组。例如:
<ul>
<li v-for="item in list">姓名:{{ item.name }},年龄:{{ item.age }}</li>
</ul>
对应的数据为:
const app = createApp({
data() {
return {
// 数组
list: [
{ name: 'zs', age: 20 },
{ name: 'ls', age: 21 },
{ name: 'wp', age: 22 }
]
}
}
})
b.v-for 中的索引
v-for 的完整语法格式为:
v-for="(循环项, 循环项的索引) in 数组"
其中 in
关键字左侧的 ( )
里面,分别是当前循环项和当前循环项的索引。例如:
<ul>
<li v-for="(item, index) in goods">{{ index + 1 }}. {{ item }}</li>
</ul>
对应的数据为
const app = createApp({
data() {
return {
// 数组
goods: ['手表', '手机', '手串']
}
}
})
注意:v-for 中的索从 0 开始递增。
c.v-for 中的解构
如果 v-for
指令中的循环项 item 是一个对象,则可以在 v-for 指令中进行解构操作,语法格式为:
v-for="{数据A, 数据B} in 数组"
或
v-for="({数据A, 数据B}, 索引) in 数组"
其中 in
关键字左侧的 { }
表示解构操作。例如:
<ul>
<li v-for="{name, age} in list">姓名:{{ name }},年龄:{{ age }}</li>
</ul>
对应的数据为:
const app = createApp({
data() {
return {
// 数组
list: [
{ name: 'zs', age: 20 },
{ name: 'ls', age: 21 },
{ name: 'wp', age: 22 }
]
}
}
})
d.template 上的 v-for
v-for
指令每次只能循环生成一个元素,如果想在每次循环期间生成一组元素,则必须在这一组元素之外包裹一层 div 标签作为容器,并把 v-for
指令作用于外层的 div 容器之上。例如:
<ul>
<div v-for="(item, index) in list">
<li class="divider" v-if="index!== 0"></li>
<li>姓名:{{ item.name }},年龄:{{ item.age }}</li>
</div>
</ul>
对应的 css 样式为:
<style>
.divider {
border-top: 1px solid #888;
list-style: none;
margin: 10px 0;
}
</style>
如上,虽然可以实现列表数据的渲染,但却不尽完美。因为 div
标签在循环中只起到容器的作用,在整个列表结构中没有任何意义。
所以推荐的做法是利用 vue 内置的 <template>
标签替代上述的 div
标签,因为 <template>
是一个虚拟的容器,不会被渲染为实际的元素。
最终,优化过后的代码如下:
<ul>
<template v-for="(item, index) in list">
<li class="divider" v-if="index!== 0"></li>
<li>姓名:{{ item.name }},年龄:{{ item.age }}</li>
</template>
</ul>
e.v-for 与 v-if 的优先级
注意:vue 官方不推荐在一个元素上,同时使用 v-if 和 v-for 指令。因为这样使用无法明确体现出二者的优先级。降低代码的阅读性和维护性。
当 v-if
和 v-for
同时存在于一个元素上的时候,v-if
比 v-for
的优先级更高。这意味着 v-if
的条件将无法访问到 v-for
作用域内定义的变量别名:
<ul>
<!-- 注意:这里的 v-if 指令中,无法访问到 item.done 对应的数据, -->
<!-- 因为 v-if 比 v-for 的优先级高, -->
<!-- 当 v-if 执行的时候访问不到 item 对象,因为 v-for 此时还未执行! -->
<li v-for="item in todos" v-if="!item.done">{{item.task}}</li>
</ul>
对应的数据为:
const app = createApp({
data() {
return {
// 任务列表,done 为 true 表示完成;done 为 false 表示未完成
todos: [
{ task: '晨练', done: true },
{ task: '吃早餐', done: true },
{ task: '吃午饭', done: false },
{ task: '午休', done: false }
]
}
}
})
解决的方案很简单,先循环再判断即可。在 li
元素的外层包裹一个 template
组件,并把 v-for
指令从 li
上挪到 template
组件上,示例代码如下:
<ul>
<template v-for="item in todos">
<li v-if="!item.done">{{item.task}}</li>
</template>
</ul>
改造后的代码除了解决了 v-if
优先级高导致的报错问题之外,还有这3个明显的特征:
1.<template>
是一个虚拟容器,不会被渲染为任何实际元素,因此不会导致 DOM 结构的冗余
2.代码的可读性更强,外层用来循环数组从而得到每个列表项,内部根据列表项的状态实现 DOM 结构的按需渲染
3.如果 item.done
值为 false
,则不会渲染对应的 DOM 结构,因此初始的渲染性能较好