Vue 自定义指令
之前在工作中很少细看vue中一些比较少用的文档,近几天注意到一个vue自定义指令,觉得挺有趣的,特此记录下一些常见自定义指令封装及用法,参考文档 vue 自定义指令。
注册及参数详解:
注册一个全局自定义指令:
Vue.directive('throttle', {
// 钩子函数,bind只调用一次,指令第一次绑定到元素时调用
bind: function (el, binding) {
// 这里处理你想做的事
}
})
组件内注册一个局部自定义指令:
directives:{
throttle:{
// 钩子函数,bind只调用一次,指令第一次绑定到元素时调用
bind: (el, binding) => {
// 这里处理你想做的事
}
}
},
自定义指令钩子函数:
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数:
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
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 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
自定义指令:
v-throttle ======= 节流
这里解释下防抖和节流共同点和区别,虽然两者都是为了防止统一内多次执行事件,消耗浏览器性能。
但节流在单位时间内只触发一次事件,防抖在单位时间内只触发最后一次事件。
应用场景:
节流,按钮点击触发多次,让其单位时间内只触发一次。
防抖,input 键入触发或window触发resize,让其单位时间内只触发最后一次。
指令封装:
const throttle = {
bind: (el, binding) => {
// console.log('指令与元素绑定')
let {
throttleFunc, onThrottle, timeout } = binding.value||{
}
timeout = timeout || 2 * 1000
let throttleTimer;
el.addEventListener('click', event => {
if (!throttleTimer) {
if(throttleFunc)throttleFunc() // 节流取消或者第一次触发时的事件
throttleTimer = setTimeout(() => {
throttleTimer = null;
}, timeout);
} else {
console.warn('处于节流,无法触发点击事件,防误触时间间隔:'+timeout+' ms')
if(onThrottle)onThrottle() // 触发节流时触发的事件
event && event.stopImmediatePropagation();
}
}, true);
},
unbind(el) {
// console.warn('指令与元素解绑')
el.removeEventListener('click', el.handler)
}
}
export default throttle
directives.js引入:
import throttle from './custom-order/throttle'
// 自定义指令
const directives = {
throttle
}
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
},
}
main.js 全局引入:
import Directives from '@/utils/directives'
Vue.use(Directives)
组件内指令使用:
<template>
<div class="hello">
<button v-throttle="options">Vue全局注册自定义指令</button><br/>
</div>
</template>
<script>
export default {
data(){
return{
options: {
onThrottle: null, // 节流时触发的事件
throttleFunc:()=>{
this.throttleTest()
},
timeout: 200
}
}
},
methods:{
throttleTest(){
console.log('触发了一个事件')
}
}
}
</script>
<style scoped>
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
</style>
ps:v-throttle=“options” 这里绑定的是作为参数传递,所以可以根据自己的需要去传递数据,比如说v-throttle=“throttleTest”,绑定一个func,然后更改throttle.js里面处理函数。
bind: (el, binding) => {
let throttleFunc = binding.value||null // 此时值为传入的 throttleTest
let timeout = timeout || 2 * 1000
let throttleTimer;
el.addEventListener('click', event => {
if (!throttleTimer) {
if(throttleFunc)throttleFunc() // 节流取消或者第一次触发时的事件
throttleTimer = setTimeout(() => {
clearTimeout(throttleTimer)
throttleTimer = null;
}, timeout);
} else {
console.warn('处于节流,无法触发点击事件,防误触时间间隔:'+timeout+' ms')
event && event.stopImmediatePropagation();
}
}, true);
}
v-debounce ======= 防抖
应用场景:
input 键入触发,让其单位时间内只触发最后一次。
指令封装:
let debounceFunc,timeout,debounceTimer
let executeFunc = async function(){
debounceTimer = await setTimeout(() => {
if(debounceFunc)debounceFunc()
}, timeout);
}
let clearTimer = function(){
clearTimeout(debounceTimer)
debounceTimer = null;
}
const debounce = {
bind: (el, binding) => {
let val = binding.value||{
}
debounceFunc = val.debounceFunc
timeout = val.timeout || 3 * 1000
el.addEventListener('input', () => {
if (debounceTimer) {
console.warn('先清除上次还未执行的延时事件')
clearTimer()
executeFunc()
}else{
executeFunc()
}
}, true);
},
unbind(el) {
el.removeEventListener('input', el.handler)
}
}
export default debounce
组件内指令使用:
<template>
<div class="hello">
<input v-debounce="debounceOptions" v-model="val">
</div>
</template>
<script>
export default {
data(){
return{
debounceOptions: {
debounceFunc:()=>{
this.debounceTest()
},
timeout: 2 * 1000
},
val: ''
}
},
methods:{
debounceTest(){
console.log('触发了一个事件')
}
}
}
</script>
<style scoped>
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
</style>
v-draggable ======= 拖拽
应用场景:
一些自定义弹窗遮住后面内容,拖拽可以使用户轻松阅读被遮挡内容。
指令封装:
const draggable = {
inserted: function (el, binding) {
let bodyClientWidth = parseInt(document.body.clientWidth)
let bodyClientHeight = parseInt(document.body.clientHeight)
let dragMoveReal = el
let dialogTop = binding.value || '15vh'
let dragMoveRealWidth = parseInt(dragMoveReal.style.width)
if (dragMoveReal.style.width && dragMoveReal.style.width.indexOf) {
// 这里是避免dragMoveReal获取的宽度为rem或者%定义的宽度
if (dragMoveReal.style.width.indexOf('rem') != -1) {
dragMoveRealWidth = parseInt(dragMoveReal.style.width) * 100
} else if (dragMoveReal.style.width.indexOf('%') != -1) {
dragMoveRealWidth = parseInt(dragMoveReal.style.width) / 100 * bodyClientWidth
}
}
let dialogLeft = (bodyClientWidth - dragMoveRealWidth) / 2
dragMoveReal.style.cursor = 'move'
dragMoveReal.style.position = 'fixed'
dragMoveReal.style.left = dialogLeft + 'px'
dragMoveReal.style.top = dialogTop
dragMoveReal.style.margin = '0'
dragMoveReal.onmousedown = function (e) {
let disx = e.pageX - dragMoveReal.offsetLeft
let disy = e.pageY - dragMoveReal.offsetTop
document.onmousemove = function (e) {
let x = e.pageX - disx
let y = e.pageY - disy
let maxX = bodyClientWidth - parseInt(window.getComputedStyle(dragMoveReal).width)
let maxY = bodyClientHeight - parseInt(window.getComputedStyle(dragMoveReal).height)
if (x < 0) {
x = 0
} else if (x > maxX) {
x = maxX
}
if (y < 0) {
y = 0
} else if (y > maxY) {
y = maxY
}
dragMoveReal.style.left = x + 'px'
dragMoveReal.style.top = y + 'px'
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null
}
}
}
}
export default draggable
组件内指令使用:
<template>
<div class="hello">
<div v-draggable class="block"></div>
</div>
</template>
<script></script>
<style>
html,body{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
.block{
width: 300px;
height: 300px;
background: red;
}
</style>