Vue.js破冰系列-3自定义指令
1 指令的格式及注册
上一章我们讲了vue的内部指令,下例是将divClicked方法添加click事件监听中,同时停止click冒泡事件:
<div v-on:click.stop="divClicked">hello</div>
由上可以得出指令的一般格式,其中指令名称为必填,其他为可选项,格式如下:
指令名[:参数][.修饰符][=表达式]
vue提供的内部指令有时不能满足我们的需求,不过vue允许我们使用directive自定义指令,自定义指令分为全局注册和组件局部注册
//全局注册
Vue.directive('指令名',{
//指令选项
})
//局部注册
var vm = new Vue({
el:'#app',
directives:{
指令名:{
//指令选项
}
}
})
全局指令使用directive命令,局部注册使用directives选项。vue会在指令名称名上加v-
前缀。如果我们注册了一个名为drag的拖拽指令,使用时应为v-drag。
Vue.directive('drag',{
//指令选项
});
//<div v-drag>可拖动块</div>
2 指令选项
指令选项定义指令的行为,这些选项是可选的,每个选项都对应一个钩子函数,一共有5种:
-
bind
在指令绑定到元素时调用,它只执行一次,可在这个选项中设置被绑定元素的监听事件等。 -
inserted
当被绑定元素插入到父节点时调用,注意:他的触发时机是插入到父节点,而不管这个父节点是否被渲染到界面中。 -
update
被绑定元素所在模板更新时调用,注意绑定元素的模板的更新,不一定是被绑定元素引起的,所以可以通过比较更新前后的值来忽略不必要的元素更新。 -
componentUpdated
被绑定元素所在模板完成一次更新周期时调用。 -
unbind
指令与元素解绑时调用,他与bind
选项对应,也是只调用一次,可在这个选项中取消被绑定元素的监听。
3 钩子函数参数
每个选项的钩子函数都会被传入以下参数:
-
el:表示指令绑定的元素,通过它可以直接操作DOM,比如我们的
v-html
指令就是通过el参数去设置它的innerHTML值。 -
binding:将指令的相关信息封装到这个对象中,比如,指令的名称name,参数arg,表达式expression,修饰符modifiers等信息,在说这个对象的key之前,我们再看一下指令的格式:
指令名name[:参数arg][.修饰符modifiers][=表达式expression]
通过这个对象可以得到指令的所有信息,具体如下:
- name 指令名称,不含
v-
前缀 - arg 指令参数
- modifiers修饰符对象,我们知道一个指令可以添加多个修饰符,这里使用对象语法来表示修饰符,如果name指令的格式是这样
v-name:arg.foo.bar
,那么,modifiers对象为{foo:true,bar:true}
,注意这是一个对象而不是数组。 - expression 表示指令的表达式,它会将表达式以字符串的形式呈现,不会去执行。
- value 是expression计算的结果。
- oldValue 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。
- name 指令名称,不含
-
vnode Vue 编译生成的虚拟节点
-
oldVnode上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
目前,我们能用到的是el和binding两个参数,vnode和oldVnode将在后续的章节中涉及。有了上面知识,我们注册一个指令的格式大致是这样的:
Vue.directive('drag',{
bind:function(el,binding){
//指令绑定到el上,只调用一次
},
insert:function(el,binding){
//el插入到父节点
},
update:function(el,binding){
//el所在模板更新
let {value,oldValue} = binding;
//可能不是el引起的(el的绑定值未变化)
if(value === oldValue){
//不是绑定值在更新前后无变化,所以不用做更新操作
return;
}
//更新操作
},
componentUpdated:function(el,binding){
//el所在模板完成一次更新周期时调用
},
unbind:function(el,binding){
//指令与el解绑,只调用一次
},
});
3 自定义指令实例
3.1 拖拽指令
一个元素要拖拽,我需要设置它的position属性为absolute,然后根据鼠标的移动位置动态的设置该元素的left和top值。本例的自定义的拖拽指令不需要指令的参数,修饰符和表达式等信息。
<style>
.box {
width:100px;
height:100px;
background:red;
}
</style>
<body>
<div id="app">
<div class="box" v-drag></div>
</div>
<script>
Vue.directive('drag', {
bind: function (el) {
//拖拽需要被绑定元素使用绝对定位
el.style.position = 'absolute';
el.onmousedown = function (e) {
//鼠标在元素内部的偏移量
let offsetX = e.offsetX;
let offsetY = e.offsetY;
document.onmousemove = function (e) {
el.style.left = e.clientX - offsetX + 'px';
el.style.top = e.clientY - offsetY + 'px';
};
el.onmouseup = function () {
document.onmousemove = null;
el.onmouseup = null;
};
};
}
});
var vm = new Vue({
el: "#app",
});
</script>
</body>
注意:这个拖拽指令设置了元素的position为绝对定位,由于绝对定位的基准点(left和top为0的点)规则为:
离自己最近的且有定位属性(relatvie,absolute,fixed,但是不含static)的祖先元素,如果没有定位属性的祖先元素,则以body为基准
而我们设置的元素的left和top的值是以document(可以理解为body元素)的位置为基础,所以,当设置了v-drag
指令的元素,存在有定位属性的祖先元素时,这个指令存在bug,因为元素的基准点不在是document的原点位置,而是祖先元素的原点位置。
3.2 格式化货币指令
有些时候需要给数字按3位加逗号的需求,我们可以使用自定义指令和正则表达式来完成这需求,如果你对下面用到的正则表达式不熟悉,可以参考我的正则表达式这篇文章。
<body>
<div id="app">
<input type="text" v-model="total">
<br>
<span v-formatter="total"></span>
</div>
<script>
var Utils = {
formmatter(el,value){
value = value + "";
let hasDot = /\./g;
let patt = /\B(?=(\d{3})+\.)/g
if (!hasDot.test(value)) {
//没有小数点的正则表达式
patt = /\B(?=(\d{3})+$)/g
}
let text = value.replace(patt, ",");
el.innerText = text;
}
}
Vue.directive('formatter', {
bind:function(el,binding){
let { value } = binding;
Utils.formmatter(el,value);
},
update: function (el, binding) {
let { value,oldValue } = binding;
if (value === oldValue) {
return;
}
Utils.formmatter(el,value)
},
});
var vm = new Vue({
el: "#app",
data: {
total: 1234567890,
}
});
</script>
</body>
上面用了bind和update两个钩子函数,一个是在初始化时格式化初始值,另一个是当total的值发生变化时,实时格式化。