指令
表达式的值除了可以出现内容中,也可以使用在其它位置,比如:属性。但是不能使用 {{}} 语法,而是需要 指令
在 vue 中,指令是一个带有 v- 前缀的属性,与普通属性不一样的地方在于,指令的值是引号括起来的 表达式,不同的指令有不同的作用,vue 内置了一些常用的指令,后期我们还可以自定义属于自己的指令
- 内容输出
- 循环
- 逻辑
- 属性绑定
- 事件
- 其它
内容输出
通过 {{}} 我们可以很方便的中模板中输出数据,但是这种方式会有一个问题,当页面加载渲染比较慢的时候,页面中会出现 {{}} ,vue 提供了几个指令来解决这个问题
指令中的表达式不需要使用
{{}}
v-text
<p v-text="title"></p>
弊端:
v-text会填充整个innerHTML
v-cloak
<p v-cloak>{{title}}</p>
需要配合 css 进行处理
<style>
[v-cloak] {
display: none;
}
</style>
v-html
为了防止 xss 攻击,默认情况下输出是不会作为 html 解析的,通过 v-html 可以让内容作为 html 进行解析
v-once
只渲染元素和组件一次,后期的更新不再渲染
v-pre
忽略这个元素和它子元素内容的编译;什么意思呢?比如我现在是需要写vue的说明文档
<!-- 如果没有使用这个v-pre指令 这个语法就被解析了 -->
<div>vue中的插值表达式(双括号):{{title}}</div>
<!-- 这样写就不会解析标签里的vue语法了 -->
<div v-pre>vue中的插值表达式(双括号):{{title}}</div>
逻辑处理
v-show
根据表达式的值(布尔值),切换元素的显示与隐藏(display 属性)
适用于状态切换比较频繁的情况
v-if
根据表达式的值(布尔值),创建或销毁元素
适用于状态切换不频繁的情况
v-else / v-else-if
与 v-else 配合
循环与列表
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, key, index) in object"></div>
v-for 中也可以使用 of 语法,在 vue 中两者没有什么区别
:key
默认情况下,在渲染 DOM 过程中使用 原地复用 ,这样一般情况下会比较高效,但是对于循环列表,特别是依赖某种状态的列表,会有一些问题,我们可以通过 :key 属性,来给每个循环节点添加一个标识
属性绑定
v-bind
绑定数据(表达式)到指定的属性上,<div v-bind:参数="值/表达式"></div>,这里的参数就是指定的属性名称
<div id="app">
<div v-bind:id="'box1'"></div>
<div v-bind:id="myId"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
myId: 'lalal'
}
})
</script>
缩写
有的一些常用指令会有对应的缩写,v-bind 对应的缩写为::
<div :id="myId"></div>
样式
针对样式属性,v-bind 值有一些特殊的写法
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>
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
单向数据流
当数据更新的时候,页面视图就会更新,但是页面视图中绑定的元素更新的时候,对应的数据是不会更新的
<input type="text" :value="title" />
我们称为:单向数据流 数据 -> 视图
在 vue 中,还有一种双向数据流绑定的方式
v-model
<input type="text" v-model="title" />
数据 title 更新,视图中 input 的 value 就会更新。同时,当 input 中的 value 更新的时候,数据 title 也会更新,这就是我们说的 数据双向绑定 [与 React 中的受控组件类似]
表单
针对一般元素,比如 div、span、p、img 等,采用的是单向绑定:v-bind,只需要把数据绑定到视图中就可以,但是对于表单这种交互性比较强的元素或组件,我们一般可能需求双向绑定,即:用户对视图元素的操作同时更新数据
v-model 在内部为不同的输入元素使用不同的属性和事件来处理数据
text和textareacheckbox和radioselect
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'
}
});
checkbox 和 radio
checkbox 和 radio 使用 checked 属性和 change 事件
单选框绑定一个值
<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: '女',
}
});
多选框绑定到一个布尔值或数组
<div id="app">
<input type="checkbox" v-model="v4" /> 同意
<hr/>
<input type="checkbox" v-model="v5" value="足球" /> 足球
<input type="checkbox" v-model="v5" value="音乐" /> 音乐
</div>
let app = new Vue({
el: '#app',
data: {
v4: true,
v5: ['足球', '音乐']
}
});
select
select 字段将 value 作为 prop 并将 change 作为事件
单选绑定到值,多选绑定到数组
<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>
</select>
</div>
指令修饰符
一个指令可以包含的内容包括:
- 指令名称
- 指令值
- 指令参数
- 指令修饰符
<组件 指令:参数.修饰符1.修饰符2="值" />
.lazy
取代 input 监听 change 事件
.number
输入字符串转为有效的数字
.trim
输入首尾空格过滤
自定义指令
我们还可以通过 Vue 提供的方法来自定义指令
注册指令
vue 提供了两种指令注册方式
- 全局指令
- 局部指令
全局指令
Vue.directive('指令名称', {指令配置});
局部指令
new Vue({
el: '#app',
directives: {
'指令名称': {指令配置}
}
});
在使用指令的时候,需要使用
v-指令名称的方式来调用
指令生命周期(钩子函数)
指令的运行方式很简单,它提供了一组指令生命周期钩子函数,我们只需要在不同的生命周期钩子函数中进行逻辑处理就可以了
- bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted : 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
- update : 所在组件更新的时候调用
- componentUpdated : 所在组件更新完成后调用
- unbind : 只调用一次,指令与元素解绑时调用
不同的生命周期钩子函数在调用的时候同时会接收到传入的一些不同的参数
- el : 指令所绑定的元素,可以用来直接操作 DOM
- binding : 一个对象,包含以下属性:
- name : 指令名,不包括
v-前缀 - value : 指令的绑定值(作为表达式解析后的结果)
- expression : 指令绑定的表达式(字符串)
- arg : 传给指令的参数,可选
- modifiers : 传给指令的修饰符组成的对象,可选,每个修饰符对应一个布尔值
- oldValue : 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用
- name : 指令名,不包括
案例
官网的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.directive('focus', {
inserted(el) {
el.focus();
}
});
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
扩展:自定义拖拽指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</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', {
bind(el, {modifiers,value}) {
let isDragStart = false;
let disX = 0;
let disY = 0;
el.canDrag = value;
el.addEventListener('mousedown', e => {
if (!el.canDrag) return;
disX = e.clientX - el.offsetLeft;
disY = e.clientY - el.offsetTop;
isDragStart = true;
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (isDragStart) {
let x = e.clientX - disX;
let y = e.clientY - disY;
if (modifiers.limit) {
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
}
el.style.left = x + 'px';
el.style.top = y + 'px';
}
});
document.addEventListener('mouseup', e => {
isDragStart = false;
});
},
componentUpdated(el, {value}) {
console.log('componentUpdated', value);
el.canDrag = value;
}
});
let app = new Vue({
el: '#app',
data: {
canDrag: false
}
});
</script>
</body>
</html>