【Vue 自定义指令】

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:只调用一次,指令与元素解绑时调用。

钩子函数参数:

  1. el:指令所绑定的元素,可以用来直接操作 DOM。
  2. 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 }。
  3. vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  4. 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>

猜你喜欢

转载自blog.csdn.net/weixin_42927679/article/details/125692604