搭建开发与调试环境
在开发 Vue 前端页面之前,首先需要搭建开发和调试环境。
Vue.is的安装有4种方式:
- 使用 CDN 方式
- 使用 NPM 方式
- 使用命令行工具(Vue CLI)方式
- 使用 Vite 方式
1. 使用 CDN 方式
CDN(Content Delivery Network,内容分发网络)是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需的内容,降低网络堵塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。
使用CDN方式来安装Vue框架,就是选择一个提供稳定Vuejs链接的CDN服务商。选择CDN后,在页面中引入Vue的代码如下:
<script src="https://unpkg.com/vue@next"></script>
2. 使用 NPM 方式
NPM 是一个 Node.js 包管理和分发工具,也是整个 Node.js 社区最流行、支持第三方模块最多的包管理器。在安装 Node.js 环境时,安装包中包含 NPM,如果安装了 Node.js,则不需要再安装NPM。
用 Vue 构建大型应用时,推荐使用 NPM 安装。NPM 能很好地和诸如 Webpack 或 Browserify 模块打包器配合使用。
使用NPM安装 Vue.js 3.x:
#最新稳定版
npm install vue@next
由于国内访问国外的服务器非常慢,而 NPM 的官方镜像就是国外的服务器,为了节省安装时间,推荐使用淘宝 NPM 镜像 CNPM,在命令提示符窗口中输入下面的命令并执行:
npm install -g cnpm --registry=https://registry.npm.taobao.org
以后可以直接使用cnpm命令安装模块,代码如下:
cnpm install 模块名称
注意:通常在开发 Vue.js 3.x 的前端项目时,多数情况下会使用 Vue CLI 先搭建脚手架项目,此时会自动安装 Vue 的各个模块,不需要使用 NPM 单独安装Vue
3. 命令行工具(CLI)
Vue提供了一个官方的脚手架(Vue CLI),使用它可以快速搭建一个应用。搭建的应用只需要几分
钟的时间就可以运行起来,并带有热重载、保存时 lint 校验以及生产环境可用的构建版本。
例如想构建一个大型的应用,可能需要将应用分割成各自的组件和文件,此时便可以使用Vue CLI快速初始化工程。
因为初始化的工程可以使用 Vue 的单文件组件,它包含各自的 HTML、JavaScript 以及带作用域的 CSS 或者 SCSS ,格式如下:
<template>
HTML
</template>
<script>
JavaScript
</script>
<stvle scoped>
css 或者 SCSS
</style>
Vue CLI工具假定用户对 Node.js 和相关构建工具有一定程度的了解。如果是初学者,建议熟悉
Vue本身之后再使用 Vue CLI工具。本书后面的章节将具体介绍脚手架的安装以及如何快速创建个项目。
4. 使用 Vite 方式
Vite 是 Vue 的作者尤雨溪开发的 Web 开发构建工具,它是一个基于浏览器原生 ES 模块导入的开发服务器。在开发环境下,利用浏览器去解析 import,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随启随用。本文后面的章节将具体介绍 Vite 的使用方法。
指令
指令是Vue模板中最常用的一项功能,它带有前缀v,主要职责是当其表达式的值改变时,应地将某些行为应用在DOM上。本章将介绍Vue的内置指令,以及自定义指令的注册与使用。
1. 内置指令
内置指令顾名思义就是 Vue 内置的一些指令,它针对一些常用的页面功能提供了以指令来封装的使用形式,以 HTML 属性的方式使用。
1.1 v-show
v-show指令会根据表达式的真假值切换元素的 display CSS属性,来显示或者隐藏元素。当条件变化时,该指令会自动触发过渡效果。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-show指令</title>
</head>
<body>
<div id="app">
<h3 v-show="ok">西瓜</h3>
<h3 v-show="no">苹果</h3>
<h3 v-show="num>=1000">库存很充足!</h3>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
ok:true,
no:false,
num:1000
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
在 Chrome 浏览器中运行程序,按 F12 键打开控制台并切换到 Elements 选项,展开<div>标签,可以发现,“苹果”并没有显示,这是因为 v-show 指令计算“no”的值为 false,所以元素不会显示。
在 Chrome 浏览器的控制台中可以看到,使用 v-show 指令,元素本身是被渲染到页面中的,只是通过 CSS 的 display 属性来控制元素的显示或者隐藏。如果 v-show 指令计算的结果为 false,则设置器样式为“display:none;”。
1.2 v-if/ v-else-if/ v-else
在 Vue 中,使用 v-if、v-else-if 和 v-else 指令实现条件判断。
1. v-if 指令
v-if 指令根据表达式的真假来有条件地渲染元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-if指令</title>
</head>
<body>
<div id="app">
<h3 v-if="ok">冰箱</h3>
<h3 v-if="no">洗衣机</h3>
<h3 v-if="num>=1000">库存很充足!</h3>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
ok:true,
no:false,
num:1000
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
在 Chrome 浏览器中运行程序,按 F12 键打开控制台并切换到Elements选项,使用 v-if="no” 的元素并没有被渲染,使用 v-if="ok” 的元素正常渲染了。也就是说,当表达式的值为 false 时,v-if 指令不会创建该元素,只有当表达式的值为 true 时,v-if 指令才会真正创建该元素。这与 v-show 指令不同,v-show 指令无论表达式真假,元素本身都会被创建,显示与否是通过CSS的样式属性display来控制的。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
2. v-else-if/v-else 指令
v-else-if 指令与 v-if 指令一起使用,用法与 JavaScript 中的 if...else if类似。
下面的示例使用 v-else-if 指令与 v-if 指令判断学生成绩对应的等级。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-else-if指令与v-if指令</title>
</head>
<body>
<div id="app">
<span v-if="score>=90">优秀</span>
<span v-else-if="score>=80">合格</span>
<span v-else-if="score>=60">及格</span>
<span v-else>不及格</span>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
ok:true,
no:false,
score:85
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
在Chrome浏览器中运行程序,按 F12 键打开控制台并切换到 Elemcnts 选项,当满足其中一个条件后,程序就不会再往下执行。使用 v-else-if 和 v-else 指令时,它们要紧跟在 v-if 或者 v-else-if 指令之后。
1.3 v-for
使用 v-for 指令可以对数组、对象进行循环,以获取其中的每一个值。
1. 使用 v-for 指令遍历数组
使用 v-for 指令必须使用特定语法 alias in expression,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名,具体格式如下:
<div v-for = "item in items">
{{item}}
</div>
下面看一个示例,使用 v-for 指令循环渲染一个数组。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-for指令遍历数组</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in nameList">
{{item.name}}--{{item.city}}--{{item.price}}元
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
nameList:[
{name:'洗衣机',city:'上海',price:'8600'},
{name:'冰箱',city:'北京',price:'6800'},
{name:'空调',city:'广州',price:'5900'}
]
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
提示:v-for 指令的语法结构也可以使用 of 替代 in 作为分隔符,例如:
<li v-for="item of nameList">
在 v-for 指令中,可以访问所有父作用域的属性。v-for 还支持一个可选的第二个参数,即当前项的索引。例如,修改上面的示例,添加 index 参数,代码如下:
<ul>
<li v-for="(item,index) in nameList">
{{index}}---{{item.name}}--{{item.score}}分--{{item.class}}
</1i>
</ul>
2. 使用 v-for 指令遍历对象
遍历对象的语法和遍历数组的语法是一样的:
value in object
其中 object 是被迭代的对象,value 是被迭代的对象属性的别名。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-for指令遍历对象</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in nameObj">
{{item}}
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
nameObj:{
name:"洗衣机",
city:"上海",
price:"6800元"
}
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
还可以添加第二个参数,用来获取键值;要获取选项的索引,可以添加第三个参数。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加第二、三个参数</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,key,index) in nameObj">
{{index}}--{{key}}--{{item}}
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
nameObj:{
name:"洗衣机",
city:"上海",
price:"6800元"
}
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
3. 使用 v-for 指令遍历整数
也可以使用 v-for 指令遍历整数。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-for指令遍历整数</title>
</head>
<body>
<div id="app">
<span v-for="item in 20">
{{item}}
</span>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
}).mount('#app');
</script>
</body>
</html>
4. 在<template>上使用 v-for
类似于 v-if,也可以利用带有 v-for 的<template>来循环渲染一段包含多个元素的内容。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>在<template>上使用v-for</title>
</head>
<body>
<div id="app">
<ul>
<template v-for="(item,key,index) in nameObj">
<li>{{index}}--{{key}}--{{item}}</li>
</template>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return{
nameObj:{
name:"洗衣机",
city:"上海",
price:"6800元"
}
}
}
}).mount('#app');
</script>
</body>
</html>
提示:template元素一般常和v-for和v-if结合使用,这样会使得整个HTML结构没有那么多多余的元素,整个结构会更加清晰。
5. 数组更新检测
Vue 将被监听的数组的变异方法进行了包裹,它们也会触发视图更新。被包裹过的方法包括push()、pop()、shift()、unshit()、splice()、sort() 和 reverse()。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>数组更新检测</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in nameList">
{{index}}--{{item}}
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return{
nameList:["洗衣机","上海","5800元"]
}
}
}).mount('#app');
通过索引向数组nameList添加“1800台”
vm.nameList[3]="1800台";
</script>
</body>
</html>
还有一些非变异方法,例如 filter()、concat() 和 slice()。它们不会改变原始数组,总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>数组更新检测</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in nameList">
{{index}}--{{item}}
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return{
nameList:["洗衣机","上海","5800元"]
}
}
}).mount('#app');
//使用数组原型的splice()方法
vm.nameList.splice(0,0,"畅销版");
</script>
</body>
</html>
6. key 属性
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且满保它们在每个索引位置正确渲染。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一 key 属性。
下面我们先来看一下不使用 key 属性的一个示例。定义一个 nameList 数组对象,使用 v-for 指令渲染到页面,同时添加三个输入框和一个添加的按钮,可以通过按钮向数组对象中添加内容。在示例中定义一个 add 方法,在方法中使用unshift()数组的开头添加元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>不使用key属性</title>
</head>
<body>
<div id="app">
<div>名称:<input type="text" v-model="names"></div>
<div>产地:<input type="text" v-model="citys"></div>
<div>价格:<input type="text" v-model="prices"><button v-on:click="add()">添加</button></div>
<hr>
<p v-for="item in nameList">
<input type="checkbox">
<span>名称:{{item.name}}—产地:{{item.city}}—价格:{{item.price}}</span>
</p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return{
names:"",
citys:"",
prices:"",
nameList:[
{name:'洗衣机',city:'北京',price:'6800元'},
{name:'冰箱',city:'上海',price:'8900元'},
{name:'空调',city:'广州',price:'6800元'}
]
}
},
methods:{
add:function(){
this.nameList.unshift({
name:this.names,
city:this.citys,
price:this.prices
})
}
}
}).mount('#app');
</script>
</body>
</html>
在Chrome浏览器中运行程序,选中列表中的第一个选项,然后在输入框中输入新的内容,单击“添加”按钮后,向数组开头添加一组新数据,页面中也相应显示,但是刚才选择的“洗衣机”变成了新添加的数据。很显然,这不我们想要的结果。
产生这种结果的原因就是 v-for 指令的“就地更新”策略,只记住了数组勾透意的索引0。当往数组中添加内容的时候,虽然数组长度增加了,但是指令只记得刚开始选择的数下标,于是就选择了新数组中下标为0的选项。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需到每项提供一个唯一 key 属性。
修改上面的示例,在 v-for 指令的后面添加 key 属性,代码如下:
<p v-for="item in nameList" v-bind:key="item.name">
<input type="checkbox">
<span>name:{{item.name}}, score:{{fitem.score}},class:{{item.class}}</span>
此时重复上面的操作,可以发现已经实现了想要的结果。
7. 过滤与排序
在实际开发中,可能一个数组需要在很多地方使用,有些地方是过滤后的数据,而有些地方是重新排列的数组。这种情况下,可以使用计算属性或者方法来返回过滤或排序后的数组。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>过滤与排序</title>
</head>
<body>
<div id="app">
<p>所有库存的商品:</p>
<ul>
<li v-for="item in nameList">
{{item}}
</li>
</ul>
<p>产地为上海的商品:</p>
<ul>
<li v-for="item in namelists">
{{item}}
</li>
</ul>
<p>价格大于或等于5000元的商品:</p>
<ul>
<li v-for="item in prices()">
{{item}}
</li>
</ul>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return{
nameList:[
{name:"洗衣机",price:"5000",city:"上海"},
{name:"冰箱",price:"6800",city:"北京"},
{name:"空调",price:"4600",city:"深圳"},
{name:"电视机",price:"4900",city:"上海"}
]
}
},
computed:{ //计算属性
namelists:function(){
return this.nameList.filter(function (nameList) {
return nameList.city==="上海";
})
}
},
methods:{ //方法
prices:function(){
return this.nameList.filter(function(nameList){
return nameList.price>=5000;
})
}
}
}).mount('#app');
</script>
</body>
</html>
8. v-for与v-if一同使用
v-for 与 v-if 一同使用,当它们处于同一节点上时,v-for 的优先级比 v-if 更高,这意味着将分别重复运行于每个 v-for 循环中。当只想渲染部分列表选项时,可以使用这种组合方式。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-for与v-if一同使用</title>
</head>
<body>
<div id="app">
<h3>已经出库的商品</h3>
<ul>
<template v-for="goods in goodss">
<li v-if="goods.isOut">
{{goods.name}}
</li>
</template>
</ul>
<h3>没有出库的商品</h3>
<ul>
<template v-for="goods in goodss">
<li v-if="!goods.isOut">
{{goods.name}}
</li>
</template>
</ul>
</div>
<script src="vue.global.js"></script>
<script>
const vm = Vue.createApp({
data() {
return {
goodss: [
{name: '洗衣机', isOut: false},
{name: '冰箱', isOut: true},
{name: '空调', isOut: false},
{name: '电视机', isOut: true},
{name: '电脑', isOut: false}
]
}
}
}).mount('#app');
</script>
</body>
</html>
1.4 v-bind
v-bind 指令主要用于响应更新 HTML 元素的属性,将一个或多个属性或者一个组件的 prop 动态绑定到表达式。
在下面的例子中,将按钮的 title 和 style 属性通过 v-bind 指令进行绑定,这里对于样式的绑定,需要构建一个对象。其他的对于样式的绑定方法,将在后面的章节中详细介绍。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-bind指令</title>
</head>
<body>
<div id="app">
<input type="button" value="按钮" v-bind:title="Title" v-bind:style="{color:Color,width:Width+'px'}">
<p><a :href="Address">超链接</a></p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return {
Title: '这是我自定义的title属性',
Color: 'blue',
Width: '100',
Address:"https://www.baidu.com/"
}
}
}).mount('#app');
</script>
</body>
</html>
1.5 v-model
v-model 指令用来在表单<input>、<textarea>及<select>元素上创建双向数据绑定,它会根据熟类型自动选取正确的方法更新元素。它负责监听用户的输入事件以及更新数据,并对一些极端场景进行特殊处理。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-bind指令</title>
</head>
<body>
<div id="app">
<!--使用v-model指令双向绑定input的值和test属性的值-->
<p><input v-model="content" type="text"></p>
<!--显示content的值-->
<p>{{content}}</p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
data(){
return {
content: "空调"
}
}
}).mount('#app');
</script>
</body>
</html>
从上面这个示例可以了解Vue的双向数据绑定,关于 v-model 指令的更多使用方法,后面的章节中还会详细讲解。
1.6 v-on
v-on 指令用于监听 DOM 事件,当触发时运行一些 JavaScript 代码。v-on 指令的表达式可以是一般的 JavaScript 代码,也可以是一个方法的名字或者方法调用语句。在使用 v-on 指令对事件进行绑定时,需要在 v-on 指令后面接上事件名称,例如 click、mousedown、mouseup等事件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-on</title>
</head>
<body>
<div id="app">
<p>
<!--监听click事件,使用JavaScript语句-->
<button v-on:click="number-=1">-1</button>
<span>{{number}}</span>
<button v-on:click="number+=1">+1</button>
</p>
<p>
<!--监听click事件,绑定方法-->
<button v-on:click="say()">古诗</button>
</p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
number:100
}
},
methods:{
say:function(){
alert("曲水浪低蕉叶稳,舞雩风软纻罗轻。")
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
在 Vue 应用中,许多事件的处理逻辑会很复杂,所以直接把 JavaSeript 代码写在 v-on 指令中是不可行的,此时就可以使用 v-on 接收一个方法,把复杂的逻辑放到这个方法中。
提示:使用 v-on 指令接收的方法名称也可以传递参数,只需要在 methods 中定义方法时说
明这个形参,即可在方法中获取到。
1.7 v-text
v-text 指令用来更新元素的文本内容。如果只需要更新部分文本内容,可使用插值来完成。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-text</title>
</head>
<body>
<div id="app">
<!--更新部分内容-->
<p>古诗欣赏:{{message}}</p>
<!--更新全部内容-->
<p v-text="message"></p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message: '百舌无言桃李尽,柘林深处鹁鸪鸣。'
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
1.8 v-html
y-html 指令用于更新元素的 innerHTML。内容按普通 HTML 插入,不会作为 Vue 模板进行编译。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-html</title>
</head>
<body>
<div id="app">
<p v-html="message"></p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message:'<h3 style="color:red"> 老去惜花心已懒,爱梅犹绕江村。</h3>'
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
注意:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只可以在可信内容上使用 v-html,禁止在用户提交的内容上使用 v-html 指令。
1.9 v-once
v-once 指令不需要表达式。v-once 指令只渲染元素和组件一次,随后的渲染使用了此指令的元素、组件及其所有的子节点,都会当作静态内容并跳过,这个特点可以用于优化更新性能。
例如,在下面的示例中,当修改 input 输入框的值时,使用了 v-once 指令的p元素不会随之变,而第二个p元素会随着输入框的内容而改变。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-once</title>
</head>
<body>
<div id="app">
<p v-once>不可改变:{{message}}</p>
<p>可以改变:{{message}}</p>
<p><input type="text" v-model = "message" name=""></p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message:"苹果"
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
1.10 v-pre
v.pre 指令不需要表达式,用于跳过这个元素和它的子元素的编译过程。可以使用 v-pre 指令来显示原始 Mustache 标签。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-pre</title>
</head>
<body>
<div id="app">
<div v-pre>{{message}}</div>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message:"竹根流水带溪云。醉中浑不记,归路月黄昏。"
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
1.11 v-cloak
v-cloak 指令不需要表达式。这个指令保持在元素上直到关联实例结束编译。和CSS规则(如[v-cloak]{display:none}) 指令一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-cloak</title>
<!-- 添加 v-cloak 样式 -->
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<p v-cloak>{{message}}</p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message:"竹根流水带溪云。醉中浑不记,归路月黄昏。"
}
}
//在指定的DOM元素上装载应用程序实例的根组件
}).mount('#app');
</script>
</body>
</html>
2. 自定义指令
自定义指令可以用来操作 DOM。尽管 Vue 使用数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何 DOM 操作,并且是可复用的。在 Vue 中,除了核心功能默认内置的指令外,Vue 也允许注册自定义指令。一般情况下普通DOM 元素进行底层操作,就会用到自定义指令。
1. 注册自定义指令
自定义指令的注册方法和组件很像,也分全局注册和局部注册,例如注册一个 v-focus 指令用于在<input>、<textarea>元素初始化时自动获得焦点,全局注册的写法如下:
//全局注册
const app = Vue.createApp({});
app.directive('focus',{
//指令选项
});
局部注册的写法如下:
//局部注册
const app = Vue.createApp({
directives:{
focus:{
//指令选项
}
}
}).mount('#app');
然后可以在模板中任何元素上使用新的 v-focus 指令,例如:
<input v-focus>
2. 钩子函数
自定义指令在 directives 选项中实现,directives选项中提供了以下钩子函数,这些钩子函数是可选的。
(1) beforeMount:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
(2) mounted:在挂载绑定元素的父组件时调用
(3) beforeUpdate:指令所在组件的VNode更新前调用
(4) update:指令所在组件的VNode及其子组件的VNode全部更新后调用
(5) beforeUnmount:在卸载绑定元素的父组件之前调用
(6) unmount:只调用一次,指令与元素解绑且父元素已卸载时调用
可以根据需求在不同的钩子函数内完成逻辑代码,例如,上面的 v-focus 指令,希望在元素插入父节点时就可以调用,最好使用mounted选项。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>v-focus</title>
</head>
<body>
<div id="app">
<input v-focus>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({ });
// 注册一个全局自定义指令 `v-focus`
vm.directive('focus', {
//当被绑定的元素插入到DOM中时
mounted: function (el) {
// 聚焦元素
el.focus()
}
})
vm.mount('#app');
</script>
</body>
</html>
在 Chrome 浏览器中运行程序,可以看到,页面在 v-focus 完成时,输入框自动获取焦点。
每个钩子函数都有几个参数可用,例如上面用到的el。它们的含义如下:
(1) el:指令所绑定的元素,可以用来直接操作 DOM。
(2) binding:一个对象,包含以下属性:
- name:指令名,不包括“v-”前缀
- value:指令的绑定值,例如v-my-directive = "1+1”,value的值是2
- oldValue:指令绑定的前一个值,仅在 upadte 和componentUpdated钩子中可用,无论值是否改变都可用。
- expression:绑定值的字符串形式。例如 v-my-directive="1+1",expression的值是"1+1"
- arg:传给指令的参数。例如 v-my-directive:foo,arg 的值是foo.
- modifiers:一个包含修饰符的对象。例如 v-my-diretive.foo.bar,修体符对象modifiers的值是{foo:true,bar:true}。
(3)vnode:Vue 编译生成的虚拟节点。
(4) oldVnode:上一个虚拟节点,仅在 update 和 beforeUpdate 钩子中可用。
注意:除了el之外,其他参数都应该是只读的,切勿进行修改。如果需要在钩子之间共数数据,则建议通过元素的 dataset 来进行。
下面自定义一个指令,在其钩子函数中输入各个参数。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>bind钩子函数的参数</title>
</head>
<body>
<div id="app">
<div v-demo:foo.a.b="message"></div>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
message: '海上生明月'
}
}
})
// 注册一个全局自定义指令 `demo`
vm.directive('demo', {
mounted (el, binding, vnode) {
let s = JSON.stringify
el.innerHTML =
'instance: ' + s(binding.instance) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
vm.mount('#app');
</script>
</body>
</html>
在 Chrome 浏览器中运行程序,由于将钩子函数的参数信息赋值给了<div>元素的 innerHTML 属性,因此会在页面中显示钩子函数的参数信息。
3. 动态指令参数
自定义的指令可以使用动态参数。例如,在v-pin:[direction]="value”中,direction参数可以根据组件实例数据进行更新,从而可以更加灵活地使用自定义指令。
下面的例子通过自定义指令来实现一个功能:让某个元素固定在页面中某个位置,在出现滚动条时,元素不会随着滚动条而滚动。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>动态指令参数</title>
</head>
<body>
<div id="app">
<!--直接给出指令的参数-->
<p v-dong:top="100">林风纤月落</p>
<!--使用动态参数-->
<p v-dong:[direction]="100">林风纤月落</p>
</div>
<!--引入vue文件-->
<script src="vue.global.js"></script>
<script>
//创建一个应用程序实例
const vm= Vue.createApp({
//该函数返回数据对象
data(){
return{
direction: 'left'
}
}
})
// 注册一个全局自定义指令 `dong`
vm.directive('dong', {
beforeMount(el, binding, vnode) {
el.style.position = 'fixed';
let s = binding.arg || 'left';
el.style[s] = binding.value + 'px'
}
})
vm.mount('#app');
</script>
</body>
</html>
3. 综合案例 - 通过指令实现下拉菜单效果
网站主页中经常需要设计下拉菜单,当鼠标移动到某个菜单上时会弹出下拉菜单列表,每个单项可以链接到不同的页面,当鼠标离开菜单列表时,菜单项会被隐藏。下面通过指令来设计这样的下拉菜单效果。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>下拉菜单</title>
<style>
body {
width: 600px;
}
a {
text-decoration: none;
display: block;
color: #fff;
width: 120px;
height: 40px;
line-height: 40px;
border: 1px solid #fff;
border-width: 1px 1px 0 0;
background: #5D478B;
}
li {
list-style-type: none;
}
#app > li {
list-style-type: none;
float: left;
text-align: center;
position: relative;
}
#app li a:hover {
color: #fff;
background: #FF8C69;
}
#app li ul {
position: absolute;
left: -40px;
top: 40px;
margin-top: 1px;
font-size: 12px;
}
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id = "app" v-cloak>
<li v-for="menu in menus" @mouseover="menu.show = !menu.show" @mouseout="menu.show = !menu.show">
<a :href="menu.url" >
{{menu.name}}
</a>
<ul v-show="menu.show">
<li v-for="subMenu in menu.subMenus">
<a :href="subMenu.url">{{subMenu.name}}</a>
</li>
</ul>
</li>
</div>
<script src="vue.global.js"></script>
<script>
const data = {
menus: [
{
name: '在线课程', url: '#', show: false, subMenus: [
{name: 'Python课程', url: '#'},
{name: 'Java课程', url: '#'},
{name: '前端课程', url: '#'}
]
},
{
name: '经典图书', url: '#', show: false, subMenus: [
{name: 'Python图书', url: '#'},
{name: 'Java图书', url: '#'},
{name: '前端图书', url: '#'}
]
},
{
name: '技术支持', url: '#', show: false, subMenus: [
{name: 'Python技术支持', url: '#'},
{name: 'Java技术支持', url: '#'},
{name: '前端技术支持', url: '#'}
]
},
{
name: '关于我们', url: '#', show: false, subMenus: [
{name: '团队介绍', url: '#'},
{name: '联系我们', url: '#'}
]
}
]
};
const vm = Vue.createApp({
data() {
return data;
}
}).mount('#app');
</script>
</body>
</html>