一、指令
表达式的值除了可以出现内容中,也可以使用在其它位置,比如:属性。但是不能使用 {{}}
语法,而是需要 指令
在 vue
中,指令是一个带有 v-
前缀的属性,与普通属性不一样的地方在于,指令的值是引号括起来的 表达式
,不同的指令有不同的作用,vue会对这些指令进行解析。vue
内置了一些常用的指令,后期我们还可以自定义属于自己的指令。
- 内容输出
- 循环
- 逻辑
- 属性绑定
- 事件
- 其它
二、内容输出
通过 {{}}
我们可以很方便的中模板中输出数据,但是这种方式会有一个问题,当页面加载渲染比较慢的时候,页面中会出现 {{}}
,vue
提供了几个指令来解决这个问题
指令中的表达式不需要使用
{{}}
2.1 v-text
<p v-text="title"></p>
弊端:
v-text
会填充整个innerHTML
{{}}和v-text的区别:
- v-text在Vue解析之前没有其他任何内容,但是{{title}}这种方式,在Vue解析之前会在页面上显示出{{title}},直到解析完成显示真正的数据内容。
- v-text会填充整个innerHTML,而{{}}只会填充当前对应的文本内容,如示例中的“!!!”
<body>
<div id="app">
<h1>{{title}}!!!</h1>
<h1 v-text="title">!!!</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el:'#app',
data:{
title:'数据标题'
}
});
</script>
</body>
2.2 v-cloak
针对v-text和{{}}两者都出现的问题({{}}在Vue解析之前会在页面上显示出{{title}},v-text会填充整个innerHTML),可以使用v-cloak进行解决(需要配合css使用) 。
解析完成后,会自动去掉v-cloak属性。
<p v-cloak>{{title}}</p>
需要配合 css 进行处理
<style>
[v-cloak] {
display: none;
}
</style>
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件基础</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h1>{{title}}!!!</h1>
<h1 v-text="title">!!!</h1>
<h1 v-cloak>{{title}}!!!</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
title: '数据标题'
}
});
</script>
</body>
</html>
2.3 v-html
为了防止 xss
攻击,默认情况下数据中的HTML输出是不会作为 html
解析的,通过 v-html
可以让数据中的html内容作为 html
进行解析。
<body>
<div id="app">
<div>{{content}}</div>
<div v-html="content"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
title: '数据标题',
content:'<div>这是v-html测试div</div>'
}
});
</script>
</body>
2.4 v-once
只渲染元素和组件一次,后期的数据更新不会再渲染。
<h1 v-once>{{title}}</h1>
2.5 v-pre
忽略这个元素和它子元素内容的编译。如有时需要在文本中原样输出{{title}}内容
<h1 v-pre>vue的v-pre测试,原样输出{{title}}</h1>
三、逻辑处理
3.1 v-show
根据表达式的值(布尔值),动态切换元素的显示与隐藏(动态添加或删除display 属性)
适用于状态切换比较频繁的情况。如选项卡切换等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指令</title>
</head>
<body>
<div id="app">
<h1 v-show="show">控制是否显示,为真则显示</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
show:true
}
});
</script>
</body>
</html>
3.2 v-if
根据表达式的值(布尔值),创建或销毁元素(为假时不会渲染,或者直接从HTML结构中销毁)
适用于状态切换不频繁的情况。如登录注销等。
v-show设置为false时,是为其添加display:none属性,而v-if条件为假时,直接不渲染该html结构 。示例如下:
<h1 v-show="show">控制是否显示,为真则显示</h1>
<h1 v-if="tab===1">h1</h1>
<h1 v-else-if="tab===2">h2</h1>
<h1 v-else="tab===3">h3</h1>
....
let app = new Vue({
el: '#app',
data: {
show:true,
tab:1
}
});
3.3 v-else / v-else-if
与 v-else
配合
<body>
<div id="app">
<h1 v-if="tab===1">h1</h1>
<h1 v-else-if="tab===2">h2</h1>
<h1 v-else="tab===3">h3</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
tab:1
}
});
</script>
</body>
四、循环与列表
4.1 v-for
根据数据循环渲染 v-for
指令所在的元素及其子元素
可以循环的数据:Array | Object | number | string | Iterable (2.6 新增)
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div>
v-for 中也可以使用 of 语法,在 vue 中两者没有什么区别
4.2 :key
默认情况下,在渲染 DOM
过程中使用 原地复用 ,这样一般情况下会比较高效,但是对于循环列表,特别是依赖某种状态的列表,会有一些问题,我们可以通过 :key
属性,来给每个循环节点添加一个标识
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指令</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="user in users" :key="user.id">{{user.name}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
users:[
{id:1,name:'lmf'},
{id:2,name:'yy'},
{id:3,name:'ll'},
]
}
});
</script>
</body>
</html>
五、属性绑定
5.1 v-bind 及其缩写":"
为什么要使用v-bind?
有时候希望data中的数据可以绑定到HTML属性上去,如id,class,style,props等。可以给属性绑定更加灵活的值(如布尔值,对象,动态切换属性值等)。v-bind可以和普通属性共存。
绑定数据(表达式)到指定的属性上(如class/id/style等),<div v-bind:参数="值/表达式"></div>
,这里的参数就是指定的属性名称
<div id="app">
<div id="myId"></div>
<div v-bind:id="'myId'"></div>
<div v-bind:id="myId"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
myId: 'kaikeba'
}
})
</script>
直接写myId或者‘myId’都会将myId以字符串形式输出,v-bind:id="myId"此处vue会将 myId作为变量进行解析,从而得到data中的数据值
缩写:
有的一些常用指令会有对应的缩写,v-bind
对应的缩写为::
<div :id="myId"></div>
5.2 样式
针对样式属性,v-bind
值有一些特殊的写法
5.2.1 style
原生普通写法
<div style="width: 100px; height: 100px; background: red"></div>
v-bind 写法
<div :style="'width: 100px; height: 100px; background: red'"></div>
对象写法
<div :style="style1"></div>
...
<script>
new Vue({
el: '#app',
data: {
style1: {
width: '100px',
height: '100px',
background: 'green'
}
}
});
</script>
数组写法
<div :style="[style1, style2]"></div>
...
<script>
new Vue({
el: '#app',
data: {
style1: {
width: '100px',
height: '100px',
background: 'green'
},
style2: {
border: '1px solid black'
}
}
});
</script>
示例:
<div style="background:red;width:100px;height:100px;">原生style测试</div>
<div :style="'background:red;width:100px;height:100px;'">v-bind:style直接输出</div>
<div :style="style1">v-bind:style对象写法</div>
<div :style="[style1,style2]">v-bind:style数组写法</div>
...
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
style1: {
background: 'green',
width: '100px',
height: '100px'
},
style2: {
border: '1px solid black'
}
}
});
</script>
解析后:
5.2.2 class
原生普通写法
<div class="box1 box2"></div>
v-bind 写法
<div :class="'box1 box2'"></div>
数组写法
<div :class="['box1', 'box2']"></div>
对象写法
<div :class="{'box1': isActive, 'box2': isChecked}"></div>
使用对象写法,可以根据值(boolean)动态添加对应的 class。使用更加灵活方便
注意class的css样式写在样式表里而不是数据data中。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>属性绑定</title>
<style type="text/css">
.box1 {
background: yellow;
width: 100px;
height: 50px;
}
.box2 {
border: 2px solid black;
}
</style>
</head>
<body>
<div id="app">
<div class="box1 box2">原生写法</div>
<div :class="'box1 box2'">:class写法</div>
<div :class="['box1', 'box2']">:class数组写法</div>
<div :class="{'box1': isActive, 'box2': isChecked}">:class对象写法</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
isActive: true,
isChecked: false
}
});
</script>
</body>
</html>
此处根据isActive为true显示box1,isChecked为false而不显示box2。
六、单项数据流
通过上面的知识点和案例,我们可以看到,当数据更新的时候,页面视图就会更新,但是页面视图中绑定的元素更新的时候,对应的数据是不会更新的,我们称为:单向数据流。即数据 -> 视图。
<body>
<div id="app">
<input type="text" :value="title">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
title:'我的标题'
}
});
</script>
</body>
单向数据流:当使用app.title="mytitle"更改数据后,视图中input框的值更改为mytitle,但是当在视图中加上“sdfsf”后,重新获取数据,数据没有改变。
在 vue 中,还有一种双向数据流绑定的方式,即v-model。
6.1 双向数据流绑定 v-model
数据 title
更新,视图中 input
的 value
就会更新。同时,当 input 中的 value
更新的时候,数据 title
也会更新,这就是我们说的 数据双向绑定 [与 React 中的受控组件类似]。
后面学习可复用性组件时会专门学习带有v-model组件及其基本原理。
<body>
<div id="app">
<input type="text" v-model="title">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
title:'我的标题'
}
});
</script>
</body>
七、表单
针对一般元素,比如 div、span、p、img 等,采用的是单向绑定:v-bind,只需要把数据绑定到视图中就可以,但是对于表单这种交互性比较强的元素或组件,我们一般可能需求双向绑定,即:用户对视图元素的操作同时更新数据
v-model 在内部为不同的输入元素使用不同的属性和事件来处理数据:
text
和textarea:
默认绑定的就是value值。且绑定的是input事件,即oninput事件触发时,就会去修改数据
checkbox
和radio:默认绑定的是checked属性和change事件。
select
7.1 text和textarea
text
和 textarea
元素使用 value
属性和 input
事件
<div id="app">
<input type="text" v-model="v1" />
<textarea v-model="v2" cols="30" rows="10"></textarea>
</div>
let app = new Vue({
el: '#app',
data: {
v1: 'aaa',
v2: 'bbb'
}
});
7.2 checkbox 和 radio
checkbox
和 radio
使用 checked
属性和 change
事件
单选框绑定一个值:
当v-model:checked="值"和value="值",两者的值一样时就会被选中。如下,如果v3的值为“男”,男就会被选中,反之相反。
<div id="app">
<input type="radio" v-model="v3" value="男" /> 男
<input type="radio" v-model="v3" value="女" /> 女
</div>
let app = new Vue({
el: '#app',
data: {
v3: '女',
}
});
多选框绑定到一个布尔值或数组 :
如果v-model的值为true,则会被选中。如同意;
或者当checkbox的value值,存在在v-model的取值中时,也会被选中。如足球和音乐
<div id="app">
<input type="checkbox" v-model="v4" /> 同意
<hr/>
<input type="checkbox" v-model="v5" value="足球" /> 足球
<input type="checkbox" v-model="v5" value="音乐" /> 音乐
<input type="checkbox" v-model="v5" value="棒球" /> 棒球
</div>
let app = new Vue({
el: '#app',
data: {
v4: true, //v-model="v4"的值为真时,被选中
v5: ['足球', '音乐'] //v-model="v5" value值在v5取值范围中时也会被选中,所以棒球不会被选中
}
});
7.3 select
select
字段将 value
作为 prop
并将 change
作为事件
单选绑定到值,多选绑定到数组
<body>
<div id="app">
<select v-model="v3">
<option value="男">男</option>
<option value="女">女</option>
</select>
<select v-model="v5" multiple>
<option value="足球">足球</option>
<option value="音乐">音乐</option>
<option value="乒乓球">乒乓球</option>
</select>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
v3:'男',
v5:['篮球','乒乓球','足球']
}
});
</script>
</body>
八、指令修饰符
一个指令可以包含的内容包括:
- 指令名称
- 指令值
- 指令参数
- 指令修饰符
<组件 指令:参数.修饰符1.修饰符2="值" />
不同的指令有不同参数,不同指令有不同的修饰符。
以下为官方内置的一些修饰符。
8.1 .lazy
取代 input
监听 change
事件。延迟响应,懒加载。
如text和textarea是即时响应的,只要修改了视图值,就会更改数据。
需求:在text框中,当失去焦点(即出发change事件)时才修改数据。
8.2 .number
输入字符串转为有效的数字。
8.3 .trim
输入首尾空格过滤
<body>
<div id="app">
<input type="text" v-model="title" /> {{title}} <br /><br />
<input type="text" v-model.lazy="title" /> {{title}} <br /><br />
<input type="text" v-model="num" /> {{num+1}} <br /><br />
<input type="text" v-model.number="num" /> {{num+1}} <br /><br />
<input type="text" v-model="title" /> ({{title}}) <br /><br />
<input type="text" v-model.trim="title" /> ({{title}})
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建根组件
let app = new Vue({
el: '#app',
data: {
title:'我的标题',
num:1
}
});
</script>
</body>
九、自定义指令
我们还可以通过 Vue
提供的方法来自定义指令
9.1注册指令
vue
提供了两种指令注册方式
- 全局指令:全局指令可以在任何地方使用。只能一个一个定义
- 局部指令:局部指令即放在某个组件中定义的指令,在哪个组件中定义就只能在哪个组件中使用。可批量定义
9.2全局指令
Vue.directive('指令名称', {指令配置});
9.3局部指令
new Vue({
el: '#app',
directives: {
'指令名称': {指令配置}
}
});
在使用指令的时候,需要使用
v-指令名称
的方式来调用 。即定义时不加v-,使用时加v-。
指令配置是根据一系列生命周期钩子函数定义的。
9.4指令生命周期(钩子函数)
指令的运行方式很简单,它提供了一组指令生命周期钩子函数,我们只需要在不同的生命周期钩子函数中进行逻辑处理就可以了。即当vue解析组件时,遇到v-前缀的特殊属性时就会去调用当前注册的指令中的各种指令配置。在不同阶段会调用不同的钩子函数:
- bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted : 被绑定元素插入父节点(渲染到页面)时调用 (仅保证父节点存在,但不一定已被插入文档中)
- update : 所在组件更新的时候调用
- componentUpdated : 所在组件更新完成后调用
- unbind : 只调用一次,指令与元素解绑时调用
不同的生命周期钩子函数在调用的时候同时会接收到传入的一些不同的参数
-
el : 指令所绑定的元素,可以用来直接操作 DOM
-
binding : 一个对象,包含以下属性:
-
name : 指令名,不包括
v-
前缀 -
value : 指令的绑定值(作为表达式解析后的结果)
-
expression : 指令绑定的表达式(字符串)
-
arg : 传给指令的参数,可选
-
modifiers : 传给指令的修饰符组成的对象,可选,每个修饰符对应一个布尔值
-
oldValue : 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用
-
9.5案例
9.5.1官网的例子
需求:当页面刷新时,组件自动接收焦点。
- autofocus:没有真假,只要有这个属性,就会自动接收焦点,不能控制其失去焦点。
- DOM操作实现:可以通过其他生命周期函数mounted实现,但是focus()函数必须在元素存在在整个页面中时才能生效(但是不理想)
- 指令实现:定义全局指令。此处需要元素在页面中渲染完成后再实现,所以需要使用inserted生命周期中进行调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指令示例-focus</title>
</head>
<body>
<div id="app">
<!-- 1.autofocus:没有真假,只要有这个属性,就会自动接收焦点,不能控制其失去焦点。 -->
<input type="text" autofocus/>
<br /><br />
<input type="text" ref="input" />
<br /><br />
<input type="text" v-focus />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 3.使用全局指令实现
Vue.directive('focus',{
// 需要元素在页面中渲染完成后再实现,所以需要使用inserted生命周期中进行调用。
inserted(el){
el.focus();
}
});
// 创建根组件
let app = new Vue({
el: '#app',
data: {
},
// 2.DOM操作实现:可以通过其他生命周期函数mounted实现,但是focus()函数必须在元素存在在整个页面中时才能生效(可以通过延迟实现,但是不理想)
// mounted(){
// setTimeout(() => {
// this.$refs.input.focus();
// }, 1000);
// }
});
</script>
</body>
</html>
9.5.2扩展:自定义拖拽指令
需求:
- 自定义全局指令,在bind()中实现可拖拽;
- 通过点击按钮,用canDrag实现是否可拖拽控制;div根据canDrag的值控制是否可拖拽(生命周期函数第二个参数binding,expression表达式即canDrag值);因为bind生命周期只在指令初始化时执行,值发生改变时,不会执行,所以使用componentUpdated()生命周期中获取最新的值;怎么和bind()函数公用这个值?使用bind()和componentUpdated()都有的el参数共享el.canDrag = value;
- 使用自定义指令修饰符,限制拖拽范围;在binding参数结构出来的modifier中获取指令修饰符,修饰符是没有值得,没有就是undefined,在mousemove时判断当前修饰符是否存在,存在时进行限制;
1.定义指令,实现可拖拽:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义拖拽指令</title>
<style>
.box {
position: absolute;
left: 100px;
top: 100px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id="app">
<div class="box" v-drag></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 自定义指令用于拖拽
Vue.directive('drag', {
bind(el) {
// 获取拖拽前鼠标位置
let disX = 0;
let disY = 0;
// 使用isStartDarg控制是否开始或结束拖拽
let isStartDarg = false;
let box = document.querySelector(".box");
// 注意是给当前组件加监听,所以是el,如果自己通过document.querySelector("div")获取div控制不了
el.addEventListener("mousedown", e => {
//box的left距离 = e.clientX(鼠标位置x) - el.offsetLeft(鼠标据box边框外侧到body外侧位置)
disX = e.clientX - el.offsetLeft;
disY = e.clientY - el.offsetTop;
isStartDarg = true;
e.preventDefault();
});
// 鼠标移动时给box位置赋值
document.addEventListener("mousemove", e => {
// 判断可拖拽才能开始拖拽
if(isStartDarg){
let x = e.clientX - disX;
let y = e.clientY - disY;
el.style.left = x + 'px';
el.style.top = y + 'px';
}
});
// 鼠标移动时给box位置赋值
document.addEventListener("mouseup", e => {
isStartDarg = false;
});
}
});
let app = new Vue({
el: '#app',
data: {
},
});
</script>
</body>
</html>
2.使用按钮canDrag控制是否可拖拽。通过bind函数的第二个参数binding可得到绑定值和传给指令的修饰符组成的对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义拖拽指令</title>
<style>
.box {
position: absolute;
left: 100px;
top: 100px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id="app">
<button @click="canDrag = !canDrag">Drag : {{canDrag}}</button>
<div class="box" v-drag.limit="canDrag"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 自定义指令用于拖拽
Vue.directive('drag', {
// v-drag="canDrag"给指令加值了才能从binding中获取值
bind(el, { modifiers, value }) {
// 获取拖拽前鼠标位置
let disX = 0;
let disY = 0;
// 使用isStartDarg控制是否开始或结束拖拽
let isStartDarg = false;
// 获取按钮绑定值和传给指令的修饰符组成的对象(注意此处value是初始化value的值)
el.canDrag = value;
// 注意是给当前组件加监听,所以是el,如果自己通过document.querySelector("div")获取div控制不了
el.addEventListener("mousedown", e => {
// 如果按钮值canDrag切换了,会通过componentUpdated进行更改el.canDrag的值
console.log(el.canDrag);
if (!el.canDrag) return;
//box的left距离 = e.clientX(鼠标位置x) - el.offsetLeft(鼠标据box边框外侧到body外侧位置)
disX = e.clientX - el.offsetLeft;
disY = e.clientY - el.offsetTop;
isStartDarg = true;
e.preventDefault();
});
// 鼠标移动时给box位置赋值
document.addEventListener("mousemove", e => {
// 判断可拖拽才能开始拖拽
if (isStartDarg) {
let x = e.clientX - disX;
let y = e.clientY - disY;
// modifiers.limit不会有值,只会有真假表是否存在,此处如果有limit指令修饰符,就限定拖拽范围
if (modifiers.limit) {
x = Math.max(0,x);
y = Math.max(0,y);
el.style.left = x + 'px';
el.style.top = y + 'px';
}
}
});
// 鼠标移动时给box位置赋值
document.addEventListener("mouseup", e => {
isStartDarg = false;
});
},
// 因为bind()方法只在第一次调用时有效,所以在按钮值更改后需要通过componentUpdated更改canDrag的值,
// 且通过bind和componentUpdated都具有的属性el进行共享数据
componentUpdated(el, { value }) {
console.log('componentUpdated', value);
el.canDrag = value;
}
});
let app = new Vue({
el: '#app',
data: {
canDrag: false
},
});
</script>
</body>
</html>
可以自己扩展v-draw画框,v-rightclick右键单击等指令。