自定义指令
自定义指令介绍directive 对普通DOM元素进行底层操作
自定义指令注册
当页面加载时,该元素将获得焦点 (注意:autofocus
在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
如果想注册局部指令,组件中也接受一个 directives
的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus
属性,如下:
<input v-focus>
自定义指令钩子函数
* bind,inserted,update,componentUpdated,unbind
* 参数 el,binding,vnode,oldvnode
指令定义函数提供几个钩子函数
-
bind:只调用一次,指令第一次绑定到元素时调用。用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
-
inserted:被绑定元素插入父节点时调用(父节点存在即可调用)。
-
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
-
componentUpdated:指令所在组件的 VNode 及其子 VNode全部更新后调用。
-
unbind:只调用一次, 指令与元素解绑时调用。
钩子函数的参数:(el, binding, vnode, oldVnode)
钩子函数参数
指令钩子函数会被传入一下参数:
-
el:指令所绑定的元素,可以用来直接操作 DOM 。
-
binding:一个对象,包含以下属性
-
name:指令名,不包含v-的前缀;
-
value:指令的绑定值;例如:v-my-directive=“1+1”,value的值是2;
-
oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子函数中可用,无论值是否改变都可用;
-
expression:绑定值的字符串形式;例如:v-my-directive=“1+1”,expression的值是’1+1’;
-
arg:传给指令的参数;例如:v-my-directive:foo,arg的值为 ‘foo’;
-
modifiers:一个包含修饰符的对象;例如:v-my-directive.a.b,modifiers的值为{‘a’:true,‘b’:true}
-
vnode:Vue编译的生成虚拟节点;
-
oldVnode:上一次的虚拟节点,仅在update和componentUpdated钩子函数中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
例子:
HTML代码:
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
JavaScript代码:
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
效果图:
动态指令参数
指令的参数可以是动态的。例如,在 v-mydirective:[argument]=“value” 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:
HTML代码:
<div id="baseexample">
<p>Scroll down the page</p>
<p v-pin="200">Stick me 200px from the top of the page</p>
</div>
JavaScript代码:
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})
new Vue({
el: '#baseexample'
})
这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
HTML代码:
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
JavaScript代码:
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
效果图:
这样这个自定义指令现在的灵活性就足以支持一些不同的用例了。
函数简写
大多数情况下,我们可能想在 bind 和 update 钩子上做重复动作,并且不想关心其它的钩子函数。可以这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法类型的 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!"
})
自定义指令–轮播
案列如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./base/swiper.min.css">
<style>
.swipe{height: 300px;background-color: aqua;}
</style>
</head>
<body>
<div id="app">
<div class="swiper-container swipe">
<div class="swiper-wrapper">
<!--内部数据datalist发生变化了,
然后生成3个swiper-slide真实dom,
插入到swiper-wrapper里面去-->
<div
class="swiper-slide"
v-for="(data,index) in dataList"
v-swiper:swipe="{current:index,length:dataList.length,swipe:'swipe'}"
>
{{data}}
</div>
</div>
<!-- 分页器 -->
<div class="swiper-pagination"></div>
</div>
</div>
<script src="./base/swiper.min.js"></script>
<script src="./base/vue.js"></script>
<script>
//有很多的插件,其实只能实例化一次,
//swiper实例化了多次之后,其实还是可以进行轮播效果,
// 但是我们发现分页器效果已经失效了。
//需求:封装一个指令v-swiper,实现内部帮助我们实例化轮播图的功能
//问题:因为有三个swiper-slide的真实dom生成然后插入,
// 所以new Swiper实例化了3次===>导致了分页器不管用了
//解决? 等最后一个元素插入完成后再去实例化不就ok了吗?
// 怎么样是代表最后一个元素插入了
//那么获取到最后一个插入的元素的下标 === 传入的datalist的数组的长度-1
//自定义指令 v-swiper
Vue.directive('swiper',{
inserted(el,binding,vnode){
// 这个地方是通过vnode.context获取到vue实例,
// 然后就可以通过这个实例.datalist.length获取到数组的长度了
// console.log(vnode.context.datalist.length)
//代表最后一个元素插入完毕了,再进进行一次实例化操作就可以了
if(binding.value.current === binding.value.length - 1){
//如果外部 {current,length,swipe:'swipe'}
//new Swiper("."+binding.value.swipe,{.....})
//默认swiper执行3次,因为插入了3个swiper-slide的真实dom
new Swiper('.' + binding.arg,{
loop:true,
pagination:{
el:'.swiper-pagination'
}
})
}
}
})
new Vue({
el:'#app',
data:{
dataList:[]//定义初始化数据
},
created(){
setTimeout(() =>{
this.dataList = ['111','222','333']//更改数据
},1000)
}
})
</script>
</body>
</html>
自定义指令钩子函数的案例展示
HTML代码:
<div id="app">
<my-comp v-if="msg" :msg="msg"></my-comp>
<button @click="update">更新</button>
<button @click="uninstall">卸载</button>
<button @click="install">安装</button>
</div>
JavaScript代码:
Vue.directive('hello', {
bind: function (el){
console.log('bind');
},
inserted: function (el){
console.log('inserted');
},
update: function (el){
console.log('update');
},
componentUpdated: function (el){
console.log('componentUpdated');
},
unbind: function (el){
console.log('unbind');
}
});
var myComp = {
template: '<h1 v-hello>{{msg}}</h1>',
props: {
msg: String
}
}
new Vue({
el: '#app',
data: {
msg: 'Hello'
},
components: {
myComp: myComp
},
methods: {
update: function (){
this.msg = 'Hi';
},
uninstall: function (){
this.msg = '';
},
install: function (){
this.msg = 'Hello';
}
}
})
a、页面加载时:bind inserted
b、更新组件:update componentUpdated
c、卸载组件:unbind
d、重新安装组件:bind inserted
注意区别:
bind与inserted:bind时父节点为null,inserted时父节点存在;
update与componentUpdated:update是数据更新前,componentUpdated是数据更新后。
详情请参考Vue自定义指令