内置指令与渲染
1. 内置指令
指令 (Directives
) 是带有 v-
前缀的特殊属性,指令属性的值预期是单个 JavaScript
表达式
当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
基本指令:v-html
、v-text
、v-show
、v-if
、v-else
、v-else-if
、v-for
、v-on
、v-bind
、v-model
、v-slot
、v-pre
、v-cloak
、v-once
1-1 v-cloak
在使用 Vue
过程中,引入 Vue
文件创建一个实例,当引入的 Vue
因为某些原因未加载完成时,未编译的 Mustache
标签就无法正常显示,只有等 Vue.js
完全加载后,才会渲染成正确的数据
- 当在文档前面引入
Vue.js
文件时会阻塞文档渲染,会出现白屏加载情况页面不会渲染,必须等Vue.js
文件加载完成才能正常显示Mustache
标签 - 当在文档后面引入
Vue.js
文件时文档会先渲染加载,文档加载Mustache
标签中还未编译无法正常显示数据,必须得等Vue.js
文件加载完成实例挂在完成才会渲染成正确的数据
<div id="app">
<h3>{{title}}</h3>
</div>
<!-- 当网络比较慢或者其它原因时如果vue.js文件在文档后引入则会显示Mustache标签并且无法正常显示数据 -->
<script src="../vue.js"></script>
<!-- 在文档前面引入vue文件时,会阻塞文档加载, 此时的渲染必须等vue.js文件加载完成才渲染 -->
<script src="../vue.js"></script>
<div id="app">
<h3>{{title}}</h3>
</div>
v-cloak
指令保持在元素上,当关联的实例对象加载完成时移除,防止出现闪屏以及未编译的 Mustache
标签情况。和 CSS
规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache
标签直到实例准备完毕
[v-cloak] {
display: none;
}
<!-- v-clock当实例对象加载完成时移除 放置白屏以及为编译的Mustache标签出现的情况 -->
<div id="app" v-cloak>
<h3>{{title}}</h3>
</div>
1-2 v-once
v-once
指令只会初始化时渲染元素和组件一次
随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过,可以用于优化更新性能
<div id="app">
<!-- v-once初始化时渲染一次,数据改变不会更新改变渲染视为静态内容跳过 -->
<h3 v-once>{{title}}</h3>
<button type="button" @click="changeName">修改内容</button>
<!-- 组件初始化渲染 -->
<my-component v-once></my-component>
</div>
<script src="../vue.js"></script>
<script>
Vue.component('my-component', {
template: `
<div>
<ul>
<li v-for="(val, index) in arr" :key="val" v-once>{{val}}</li>
</ul>
<button type="button" @click="changArr">修改内容</button>
</div>`,
data() {
return {
arr: ['html', 'css', 'js']
}
},
methods: {
changArr() {
this.arr.push('vue')
}
}
})
const vm = new Vue({
el: '#app',
data: function () {
return {
title: 'Hello Vue'
}
},
methods: {
changeName() {
this.title = 'Hello JSX'
}
}
})
</script>
1-3 v-html和v-text
v-html
指令更新元素的innerHTML
。内容按普通HTML
插入不会作为Vue
模板进行编译v-text
指令更新元素的textContent
。将数据以字符串文本的形式更新- 更新部分文本内容可以使用插值语法更新,与
v-html
和v-text
不同的是:插值表达式只会更新原本占位插值所在的数据内容,而v-html
和v-text
指令则是替换掉整个内容
<div id="app">
<!-- v-html更新元素HTML标签 替换整个内容 -->
<div v-html="html">内容</div>
<!-- v-text更新元素文本内容 替换整个内容 -->
<h3 v-text="text">内容</h3>
<!-- mustache标签文本内容更新 替换原始占位插值的数据内容 -->
<h3>内容{{mustache}}</h3>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
html: '<h3>Hello v-html</h3>',
text: 'Hello v-text',
mustache: 'Hello mustache'
}
}
})
</script>
1-4 v-bind和v-on
v-bind
指令用来动态绑定元素的属性(img
的src
、title
属性等)与样式(可以用style
形式进行内联样式绑定,也可以通过class
形式进行类名绑定样式)以及绑定prop
<div id="app">
<!-- v-bind动态绑定属性、class、样式 -->
<a v-bind:href="link">百度</a>
<h3 v-bind:style="headTitleStyle" v-bind:class="headTitleClass">绑定class与style</h3>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
link: 'https://www.baidu.com',
headTitleStyle: {
color: '#736af7',
fontSize: '30px'
},
headTitleClass: ['headtitle', 'box'],
title: 'v-on'
}
}
})
</script>
v-on
指令用来绑定事件监听器监听DOM
事件,用在普通元素上时,只能监听原生DOM
事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件
<div id="app">
<!-- v-on绑定DOM事件 -->
<h3>{{title}}</h3>
<button type="button" @click="changValue">修改内容</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'v-on'
}
},
methods: {
changValue() {
this.title = '绑定DOM事件'
}
}
})
</script>
1-5 v-pre
v-pre
指令用来跳过这个元素和它的子元素的编译过程
- 可以用来显示原始
Mustache
标签 - 跳过大量没有指令的节点、没有使用插值语法的节点会加快编译
<div id="app">
<!-- v-pre指令跳过元素以及子元素的编译 -->
<h3 v-pre>{{title}}</h3>
<!-- 没有指令没有插值语法的节点使用会加快编译 -->
<h3 v-pre>我是JSX</h3>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'v-pre指令'
}
}
})
</script>
2. 条件渲染
v-if
与 v-show
可以实现条件渲染,Vue
会根据表达式值的真假条件来渲染元素。还有 v-else
、v-else-if
指令类似于 JavaScript
中的 if-else
、if-else-if
2-1 v-if指令
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy
值的时候被渲染
在
JavaScript
中,truthy
(真值)指的是在布尔值上下文中转换后的值为真的值。所有值都是真值,除非它们被定义为falsy
(即除了false
、0
、''
、null
、undefined
和NaN
的值)
<!-- v-if根据指令内的表达式条件渲染元素内容 -->
<!-- 当元素值为真值时渲染,值为假值时不渲染 -->
<h3 v-if="true">Hello Vue Boolean值</h3>
<h3 v-if="1 === 1">Hello Vue 等号运算符</h3>
<!-- 值为假值无法渲染 -->
<h3 v-if="1 > 2">Hello Vue</h3>
<h3 v-if="true ? true : false">Hello Vue 三元表达式</h3>
2-2 <template>
元素
v-if
是一个指令,必须将它添加到一个元素上
切换多个元素时,可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
,最终的渲染结果将不包含 <template>
元素
<div id="app">
<!-- 当需要切换多个元素使用时 -->
<!-- 1. 在元素外嵌套一个元素用来条件渲染 -->
<div v-if="true">
<h3>html</h3>
<h3>css</h3>
<h3>js</h3>
</div>
<!-- 2. 通过template元素包裹,该元素在页面渲染中不会显示 -->
<template v-if="true">
<h3>vue</h3>
<h3>webpack</h3>
<h3>node</h3>
</template>
</div>
2-2 v-else指令
v-else
指令来表示 v-if
的 else
块
v-else
元素必须紧跟在带v-if
或者v-else-if
的元素的后面,否则它将不会被识别
<div id="app">
<!-- v-else指令用于表示v-if的else语句块 -->
<!-- 当v-if不成立时则使用v-else语句块内容 -->
<h3 v-if="Math.random() > 0.5">Hello Vue</h3>
<h3 v-else>Hello JSX</h3>
<!-- 必须在v-if与v-else-if指令后, 否则无法识别-->
<div>Hello JavaScript</div>
<div v-else>Hello Webpack</div>
</div>
2-3 v-else-if指令
v-else-if
指令来表示 v-if
的 else-if
块,可以连续使用
类似于
v-else
,v-else-if
也必须紧跟在带v-if
或者v-else-if
的元素之后
<!-- v-if用于表示v-if的 else-if 语句块 可以连续使用 -->
<button type="button" @click="num++">num++</button>
<h3 v-if="num === 0">html</h3>
<h3 v-else-if="num === 1">css</h3>
<h3 v-else-if="num === 2">javascript</h3>
<div>必须紧跟v-if或v-else-if</div>
<!-- 必须跟在v-if和v-else-if指令后 -->
<h3 v-else-if="num === 3">react</h3>
<h3 v-else>vue</h3>
el: '#app',
data: function() {
return {
num: 0
}
}
2-4 key管理复用元素
Vue
会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染
当两个模版使用了相同的元素的情况下,通过条件判断渲染时会复用元素,而不是重新渲染元素,这会在某些特定场景不符合实际需求
Vue
提供了一种方式来表达这两个元素是完全独立的,不要复用,添加一个具有唯一值的 key
属性标识即可
<!-- 使用key后可以区分元素,可以使元素独立不被复用 -->
<template v-if="isTrue">
<label for="">用户名</label>
<input type="text" placeholder="username" key="username">
</template>
<template v-else>
<label for="">邮箱</label>
<input type="text" placeholder="email" key="email">
</template>
<button type="button" @click="toggle">切换toggle</button>
el: '#app',
data: function() {
return {
isTrue: true,
title: 'Hello Vue',
message: 'Hello React'
}
},
methods: {
toggle() {
this.isTrue = !this.isTrue
}
}
2-5 v-show指令
v-show
用于根据条件展示元素的选项
带有 v-show
的元素始终会被渲染并保留在 DOM
中。v-show
只是简单地切换元素的 CSS
属性中的 display
属性
<!-- v-show用于展示元素的选项 -->
<!-- v-show指令相当于切换元素的display属性 -->
<h3 v-show="true">Hello Vue</h3>
<h3 v-show="false">Hello Vue</h3>
2-6 v-if与v-show区别
v-if
是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建v-if
是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块- 相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS
进行切换 - 一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好
3. 列表渲染
在使用 v-for
指令时,可以对数组、对象、数字、字符串进行循环,来获取源数据中的每一个值
3-1 v-for数组
可以用 v-for
指令基于一个数组来渲染一个列表
v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名
- 在
v-for
块中,我们可以访问所有父作用域的属性 - 循环数组时可使用
of
替代in
作为分隔符来迭代,数组遍历有两个参数value
—— 数组循环出的数组项index
—— 当前项的索引
<ul>
<!-- v-for遍历数组 -->
<li v-for="(list, index) of arr">
<!-- v-for块内元素可访问所有父级作用域属性 -->
<h3>{{index}} {{list.name}}-{{list.message}}</h3>
</li>
<!-- index表示数组项索引 list表示遍历的数组项 -->
</ul>
el: '#app',
data: function() {
return {
arr: [
{name: '林俊杰', message: '新地球'},
{name: '邓紫棋', message: '新的心跳'},
{name: '薛之谦', message: '丑八怪'}
]
}
}
3-2 v-for对象
可以用 v-for
指令遍历一个对象的属性
在遍历对象时,会按
Object.keys()
的结果遍历,但是不能保证它的结果在不同的JavaScript
引擎下都一致
value
—— 遍历对象的属性值name
—— 遍历对象的属性名index
—— 遍历对象的索引
<ul>
<!-- 通过v-for遍历对象 遍历对象通过in分隔符 -->
<li v-for="(value, key, index) in info">
<h3>{{value}}-{{key}}-{{index}}</h3>
<!-- value表示对象的属性值 key表示对象的属性名 index表示属性的索引 -->
</li>
</ul>
el: '#app',
data: function() {
return {
// 定义对象数据
info: {
title: 'Hello Vue',
author: 'Jiang Fei',
date: '2022-05-16'
}
}
}
3-3 v-for使用
- 在
v-for
里使用值范围,可以接受整数。在这种情况下,它会把模板重复对应次数 - 在
<template>
上使用v-for
,渲染列表时,可以把一个<template>
元素当做不可见的包裹元素,并在上面使用v-for
,最终的渲染结果将不包含<template>
元素
<!-- v-for接受范围值,模版会重复渲染 -->
<h3 v-for="i in 5">{{i}}</h3>
<!-- 使用template元素,包裹渲染元素,渲染结果不会存在template元素 -->
<template v-for="(list, index) in lesson">
<h3>{{list.name}}</h3>
<h3>——————————————</h3>
</template>
el: '#app',
data: function() {
return {
lesson: [
{name: 'html', isShow: false},
{name: 'css', isShow: false},
{name: 'javascript', isShow: true},
{name: 'vue', isShow: true}
],
}
}
3-4 v-for里的key的作用
当 Vue
正在更新使用 v-for
渲染的元素列表时,它默认使用就地更新的策略,如果数据项的顺序被改变,Vue
将不会移动 DOM
元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染
为了给 Vue
一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
属性
key
属性主要用在 Vue
的虚拟 DOM
算法,在新旧 nodes
对比时辨识 VNodes
比较差异
- 如果不使用
key
,Vue
会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法 - 使用
key
时,它会基于key
的变化重新排列元素顺序,并且会移除key
不存在的元素
key
是为 Vue
中 vnode
的唯一标记,通过这个 key
,diff
操作可以更准确、更快速
- 更准确:因为带
key
就不是就地复用了,在sameNode
函数a.key === b.key
对比中可以避免就地复用的情况。所以会更加准确 - 更快速:利用
key
的唯一性生成map
对象来获取对应节点,比遍历方式更快
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值
<ul>
<!-- key作为元素唯一标识 当列表发生改变用来重用或者重新排序元素 -->
<li v-for="(list, index) of lesson">
<!-- key值不能使用引用类型,只能是字符串与数值类型 -->
<div>{{list.name}} <input type="text"></div>
<!-- 当不指定key时,元素会复用相同类型的元素 -->
</li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function() {
return {
title: 'Hello Vue',
lesson: [
{name: 'html', maxLength: 70},
{name: 'css', maxLength: 70},
{name: 'js', maxLength: 80 }
]
}
},
methods: {
addArr() {
this.lesson.splice(2, 0, {name: 'vue', maxLength: 50})
}
}
3-5 索引作为key的问题
不要使用数组的下标
index
作为key
- 数组下标
index
作为key
时,当对数据进行逆序添加、逆序删除时会破坏数据顺序从而导致产生没必要的DOM
更新
<ul>
<!-- 数组下标index作为key时, -->
<li v-for="(list, index) of lesson" :key="index">
<!-- <li v-for="(list, index) of lesson" :key="list.id"> -->
<div>{{list.name}}-{{list.value}}</div>
</li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function () {
return {
title: 'Hello Vue',
lesson: [
{ id: 01, name: 'html', value: 70 },
{ id: 02, name: 'css', value: 80 },
{ id: 03, name: 'js', value: 60 }
]
}
},
methods: {
addArr() {
this.lesson.splice(2, 0, { id: 04, name: 'vue', value: 55 })
}
}
- 当元素结构中有
input
输入类表单元素时,会产生错误的DOM
更新,导致Vue
会复用错误的旧子节点
<ul>
<!-- 数组下标index作为key时, -->
<li v-for="(list, index) of lesson" :key="index">
<!-- <li v-for="(list, index) of lesson" :key="list.id"> -->
<div>{{list.name}}-{{list.value}} <input type="text"></div>
</li>
</ul>
<button @click.once="addArr">添加数据</button>
el: '#app',
data: function () {
return {
title: 'Hello Vue',
lesson: [
{ id: 01, name: 'html', value: 70 },
{ id: 02, name: 'css', value: 80 },
{ id: 03, name: 'js', value: 60 }
]
}
},
methods: {
addArr() {
this.lesson.splice(2, 0, { id: 04, name: 'vue', value: 55 })
}
}
3-6 数组更新检测
Object
对象的侦测方式是通过 getter/setter
来实现的
Array
数组的侦测无法通过 getter/setter
来完成,Vue
对将被侦听的数组的变更方法进行了包裹,只有包裹的方法操作数组时才会触发视图更新
push()
—— 添加任意数量数组项,依次添加到数组末尾,并返回修改后数组长度pop()
—— 从数组末尾删除最后一项,并减少数组的长度,返回移除后的数组项shift()
—— 从数组开头删除第一项并返回该项,同时数组的长度减少unshift()
—— 添加任意数量数组项,依次添加到数组开头,并返回修改后数组长度splice()
—— 删除原数组一部分数据项,可以在被删除的位置插入新的数组项sort()
—— 调用每个数组项的toString()
方法,转换为字符串进行比较排序,并返回排序过后的数组reverse()
—— 用于反转数组排序,返回反转排序后的数组
通过包裹数组更新元素的方法实现原理:
-
调用原生对应的方法对数组进行更新
-
重新解析模板,进而更新页面
<!-- 对象是通过getter与setter来实现数据监测的 -->
<!-- Vue能检测嵌套对象的变化 -->
<h3>{{message.title}} {{message.info.name}}</h3>
<ul>
<li v-for="list in arr">
<h3>{{list}}</h3>
</li>
</ul>
<div>
<button @click="changObj">修改对象数据</button>
<button @click="changeArr">修改数组数据</button>
</div>
el: '#app',
data: function() {
return {
message: {
title: 'Hello Vue',
info: {
name: 'jsx',
age: 22
}
},
arr: ['html', 'css', 'js']
}
},
methods: {
changObj() {
// 直接修改对象属性,Vue会检测到数据修改并更新视图
this.message.info = {
name: 'ljj',
age: 23
}
console.log(this.message.info)
},
changeArr() {
// 通过[]修改数组。Vue无法检测数组变化,数据改变但不改变视图
// this.arr[0] = 'vue'
// 必须通过push, pop, shift, unshift, splice, sort, reverse方法操作才会触发视图更新
this.arr.splice(this.arr.length, 0, 'Vue')
console.log(this.arr)
}
}
3-7 过滤与排序
变更方法会改变调用了这些方法的原始数组,非变更方法则是返回一个新的数组。使用非变更方法时,可以将新数组替换旧数组
concat
—— 创建新数组,将传入的数据添加到新数组的末尾,最后返回这个添加后的新数组slice
—— 基于当前数组一个项或者多个项创建一个新数组,接收两个参数,即要返回项的起始和结束位置,最后返回新数组map
—— 对数组每一项执行指定函数,返回每次函数调用的结果组成的数组filter
—— 对数组每一项执行指定函数,该函数返回符合条件的项组成的数组
当需要展示数组过滤或者排序后的内容,而不实际变更或重置原始数据,在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组
<label for="">学习课程</label>
<input type="text" name="keyword" v-model="keyword">
<button type="button" @click="sortType = 1">升序</button>
<button type="button" @click="sortType = 2">降序</button>
<button type="button" @click="sortType = 0">原顺序</button>
<ul>
<li v-for="list in selectStudy">
<h3>{{list.lesson}} <----> 价格:{{list.money}}元</h3>
</li>
</ul>
el: '#app',
data: function () {
return {
keyword: '',
sortType: 0, // 0原顺序,1升序,2降序
study: [
{ lesson: 'html', money: 50 },
{ lesson: 'css', money: 80 },
{ lesson: 'javascript', money: 120 },
{ lesson: 'vue', money: 90 }
]
}
},
// 使用计算属性实现过滤与排序
computed: {
selectStudy() {
const newArr = this.study.filter((val) => {
return val.lesson.includes(this.keyword) === true
})
if (this.sortType) {
newArr.sort((a, b) => {
return this.sortType === 1 ? b.money - a.money : a.money - b.money
})
}
return newArr
}
}
也可以使用 watch
监听去实现,当数据发生改变时去执行过滤或者排序操作,然后返回过滤或排序后的数组
<label for="">学习课程</label>
<input type="text" name="keyword" v-model="keyword">
<button type="button" @click="sortType = 1">升序</button>
<button type="button" @click="sortType = 2">降序</button>
<button type="button" @click="sortType = 0">原顺序</button>
<ul>
<li v-for="list in studyArr">
<h3>{{list.lesson}} ---- 价格:{{list.money}}元</h3>
</li>
</ul>
el: '#app',
data: function () {
return {
keyword: '',
sortType: 0, // 0原顺序,1升序,2降序
study: [
{ lesson: 'html', money: 50 },
{ lesson: 'css', money: 80 },
{ lesson: 'javascript', money: 120 },
{ lesson: 'vue', money: 90 }
]
}
},
// 使用监听器实现过滤与排序
watch: {
keyword: {
immediate: true,
handler(val) {
this.studyArr = this.study.filter((val) => {
return val.lesson.includes(this.keyword) === true
})
}
},
sortType: {
immediate: true,
handler(val) {
this.studyArr = this.study.filter((val) => {
return val.lesson.includes(this.keyword) === true
})
if (val) {
this.studyArr.sort((a, b) => {
return val === 1 ? b.money - a.money : a.money - b.money
})
}
return this.studyArr
}
}
}
3-8 v-for与v-if
不推荐在同一元素上使用
v-if
和v-for
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中
如果你的目的是有条件地跳过循环的执行,那么可以将 v-if
置于外层元素
v-for
渲染符合v-if
条件判断的数据
<!-- v-for同一节点上优先级高于v-if -->
<!-- 可将v-if作为条件判断该循环那些元素 -->
<ul>
<li v-for="list in lesson" v-if="list.isShow">
<h3>{{list.name}}</h3>
</li>
</ul>
el: '#app',
data: function() {
return {
lesson: [
{name: 'html', isShow: false},
{name: 'css', isShow: false},
{name: 'javascript', isShow: true},
{name: 'vue', isShow: true}
]
}
}
- 通过
v-if
条件判断跳过v-for
循环的执行
<!-- 通过v-if条件判断跳过v-for循环 -->
<ul v-if="info.length">
<li v-for="list in info" :key="list.name">
<h3>{{list.name}}</h3>
</li>
</ul>
<h3 v-else>没有数据</h3>
<button type="button" @click.once="addList">添加数据</button>
el: '#app',
data: function() {
return {
info: []
}
},
methods: {
addList() {
this.info.splice(0, 0,
{name: 'html', isShow: false},
{name: 'css', isShow: false},
{name: 'javascript', isShow: true},
{name: 'vue', isShow: true})
}
}
3-9 组件上使用v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for
直接在组件上绑定属性不会有效,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域,为了把迭代数据传递到组件里,我们要使用 prop
当在组件上使用
v-for
时,key
现在是必须的
<template>
<my-component :todo="todoList"></my-component>
</template>
Vue.component('my-component', {
// 在组件上使用v-for需要传递props, 组件上绑定的数据不会传入到组件内
props: ['todo'],
template: `<ul><li v-for="list in todo" :key="list.name">{{list.name}}</li></ul>`
})
el: '#app',
data: function() {
return {
lesson: [
{name: 'html', isShow: false},
{name: 'css', isShow: false},
{name: 'javascript', isShow: true},
{name: 'vue', isShow: true}
]
}
}
4. 自定义指令
4-1 什么是自定义指令?
在 Vue
的项目中, v-if
、v-show
、v-for
或 v-model
等等称之为指令,也被称之为 Vue
的内置指令,它们为我们提供了不同的功能可以直接使用的
为了更好的满足需求,最大化的让开发者个性化开发,Vue
暴漏了自定义指令的 API
给我们使用,让我们除了使用内置指令外,我们还可以自己定义指令,定义好后和内置指令的方式非常类似
- 自定义指令中命名必须小写,当有多个单词时不要使用驼峰式形式,要是有短横线小写形式
directive
中当指令名为短横线小写形式时,需要使用引号包裹不然无法使用
<!-- v-开头的指令被称为内置指令 -->
<h3 v-text="title"></h3>
<!-- Vue允许自定义指令 自定义属性在模版使用时使用小写 在directive上建议使用单引号包裹 -->
<h3 v-wenben:write="value"></h3>
el: '#app',
data: function () {
return {
title: 'v-text指令',
value: '自定义指令'
}
},
directives: {
// 该写法只在bind和update时触发
wenben: function(el, binding) {
console.log(el, binding)
el.innerText = binding.value
}
}
4-2 局部与全局自定义指令
**Tips:**全局注册指令使用的是
directive
,局部注册指令使用的是directives
,局部指令一次性注意注册很多个,全局指令依次只能注册一个
- 局部自定义指令,在
Vue
中提供了一个directives
选项供我们注册局部自定义指令,该指令只允许在组件内部使用 - 全局自定义属性,通过
Vue.directive
注册全局自定义指令,可以在任意组件中的元素上使用自定义指令
<div id="app">
<h3 v-wenben="title"></h3>
<!-- v-texts指令是在全局定义任何地方都可使用 -->
<h3 v-texts="title"></h3>
</div>
<div id="box">
<!-- v-wenben指令是在vm实例定义的不能在全局使用 -->
<!-- <h3 v-wenben="title"></h3> -->
<h3 v-texts="title"></h3>
</div>
// 1. directive和directives参数可以为函数或者对象
// 2. 直接传入函数则表示在update和binding函数触发时会执行,其它钩子触发不会执行
// 通过Vue.directive在全局中定义
Vue.directive('texts', {
// 指令第一次绑定元素时触发
bind(el, binding) {
el.innerText = binding.value
},
// 组件虚拟DOM更新时调用
update(el, binding) {
el.innerText = binding.value
}
})
// vue实例1
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'Hello Directive'
}
},
directives: {
// 局部定义通过directives选项在options中定义
wenben: function(el, binding) {
el.innerText = binding.value
}
}
})
// vue实例2
const vm1 = new Vue({
el: '#box',
data: function() {
return {
title: 'Hello Vue'
}
}
})
4-3 自定义指令钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)update
:所在组件的VNode
虚拟DOM
更新时调用,但是可能发生在其子VNode
虚拟DOM
更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新componentUpdated
:指令所在组件的VNode
及其子VNode
虚拟DOM
全部更新后调用unbind
:只调用一次,指令与元素解绑时调用,通过$destroy()
销毁一个组件的实例解绑所有指令与事件
**Tips:**钩子函数里的
this
指向的是全局window
对象
<h3>
<my-component :title="title"></my-component>
</h3>
<button type="button" @click="changeTitle">修改数据</button>
<button type="button" @click="destroy">实例销毁</button>
Vue.component('my-component', {
template: `<div>
<h3 v-wenben="getTitle"></h3>
</div>`,
data() {
return {
}
},
props: ['title'],
computed: {
getTitle() {
console.log(this.title)
return this.title
}
}
})
// 自定义指令钩子函数中this都指向window
Vue.directive('wenben', {
// 指令与元素初始化绑定触发
bind(el, binding) {
console.log('bind')
console.log(this)
el.innerText = binding.value
},
// 被绑定元素插入父节点时触发
inserted(el, binding) {
console.log('inserted')
console.log(this)
el.innerText = binding.value
},
// 虚拟DOM更新时触发
update(el, binding) {
console.log('update')
console.log(this)
el.innerText = binding.value
},
// 组件以及子组件虚拟DOM全部更新完成触发
componentUpdated() {
console.log('componentUpdated')
console.log(this)
},
unbind() {
// 指令与元素解除绑定时触发
console.log('unbind')
console.log(this)
}
})
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'hello vue'
}
},
methods: {
changeTitle() {
this.title = 'Hello JSX'
},
destroy() {
// 组件实例销毁时解除绑定指令与事件
this.$destroy()
}
}
})
4-4 钩子函数参数
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作DOM
binding
:一个对象,包含以下属性: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 编译生成的虚拟节点oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用
除了
el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset
来进行
<div id="app">
<h3 v-wenben:show.end="title + '壮壮'"></h3>
</div>
el: '#app'
data: function() {
return {
title: 'hello vue'
}
},
directives: {
wenben: {
bind(el, binding, vnode, oldNode) {
console.log(el) // 指令绑定的元素
console.log(binding) // 对象包含多个属性
console.log(vnode) // 编译生成的虚拟DOM
console.log(oldNode) // 上一个虚拟DOM的节点只能在update或者componentUpdate使用
el.innerText = binding.value
},
update(el, binding, vnode, oldNode) {
console.log(oldNode); // 能获取上一个虚拟DOM节点属性内容
el.innerText = binding.value
}
}
},
methods: {
changeValue() {
this.title = 'Hello Vue'
}
}
4-5 动态指令参数
指令的参数可以是动态的,参数可以根据组件的实例属性进行更新
<!-- <div v-mydirective:[argument]="value"></div> -->
<h3 v-pos:[title]="20">Hello Vue</h3>
el: '#app'
data: function() {
return {
// 通过[]自定义指令参数,将定义的date数据作为指令参数实现动态指令参数
title: 'bottom'
}
}
directives: {
pos: {
bind(el, binding) {
el.style.position = 'fixed';
let result = binding.arg === 'bottom' ? 'bottom' : 'top';
el.style[result] = binding.value + 'px'
}
}
}
4-6 简写与对象字面量
自定义指令 directive
中可以简写参数,可以直接传入一个函数作为钩子函数,相当于指定 bind
与 update
钩子函数
// 全局自定义指令简写
Vue.directive('focus', function(el, binding) {})
// 全局完整形式
Vue.directive('focus', {
bind() {},
inserted() {},
update() {},
componentUpdate() {},
unbind() {}
})
const vm = new Vue({
el: '#app',
// 局部自定义指令简写
directives: {
// 简写形式,相当于定义bind与update钩子,其它钩子函数不会触发执行
focus: function(el, binding) {},
// 局部自定义指令完整钩子函数
focus: {
bind() {},
inserted() {},
update() {},
componentUpdate() {},
unbind() {}
}
}
})
如果一个指令需要多个值可以传入对象作为自定义指令的内容,指令函数能接受任何 JavaScript
表达式
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
4-7 实现一个自定义指令
实现一个拖拽指令,可在页面可视区域任意拖拽元素
- 设置需要拖拽的元素为相对定位,其父元素为绝对定位
- 鼠标按下
(onmousedown)
时记录目标元素当前的left
和top
值 - 鼠标移动
(onmousemove)
时计算每次移动的横向距离和纵向距离的变化值,并改变元素的left
和top
值 - 鼠标松开
(onmouseup)
时完成一次拖拽
Vue.directive('draggable', {
inserted: function(el) {
el.style.cursor = 'move'
el.onmousedown = function(e) {
let disx = e.pageX - el.offsetLeft
let disy = e.pageY - el.offsetTop
document.onmousemove = function(e) {
e.preventDefault();
let x = e.pageX - disx
let y = e.pageY - disy
let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el)
.width)
let maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el)
.height)
if (x < 0) {
x = 0
} else if (x > maxX) {
x = maxX
}
if (y < 0) {
y = 0
} else if (y > maxY) {
y = maxY
}
el.style.left = x + 'px'
el.style.top = y + 'px'
el.style.position = 'fixed'
}
document.onmouseup = function(e) {
e.preventDefault();
document.onmousemove = document.onmouseup = null
}
}
}
})