11.自定义指令directive

148 阅读3分钟

directive

vue官方提供了v-if,v-model等指令方便大家快捷开发,还提供了供开发者自己diy的自定义指令directive

自定义指令中有几个钩子

  • created:在绑定元素的属性前,或者事件监听器应用前调用
  • beforeMount:在元素被插入到DOM前调用,例如我们想要实现输入框的自动聚焦,就不能在beforeMount钩子中实现
  • mounted:在绑定元素的父组件以及自己的所有子节点都挂载完毕后调用,这个时候DOM已经渲染出来,我们实现输入框自动聚焦也是在这个钩子函数中实现
  • beforeUpdate:绑定元素的父组件更新前调用
  • updated:在绑定元素的父组件以及自己的所有子节点都更新完毕后调用
  • beforeUnmount:绑定元素的父组件卸载前调用
  • unmounted:绑定元素的父组件卸载后调用 也可以不用对象,简写成一个函数 下面的实例简写为一个函数

钩子里的参数

  • el:指令绑定到的DOM元素,可以用于直接操作当前元素,默认传入钩子的就是el参数,例如我们开始实现的focus指令,就是直接操作的元素DOM

  • binding:这是一个对象,包含以下属性:

    • value:在元素上使用指令时,传递给指令的值。
    • oldValue:之前的值,一般用于beforeUpateupdated钩子函数中,例如:beforeUpdate(el, {oldValue: ''})
    • arg:传递给指令的参数,非必需,例如<div v-reverse:foo="'hello'"></div>,那么传递给指令的参数就是foo
    • modifiers:一个由修饰符构成的对象,例如<div v-reverse.foo.bar="'hello'"></div>,那么该修饰符对象为{foo: true, bar: true},我们经常使用到的事件修饰符,其实和这个差不多。
    • instance:使用该指令的组件实例,注意不是DOM
    • dir:指令的定义对象
  • vnode:绑定元素的地城VNode

  • preVnode:之前的渲染中代表指令所绑定的元素的VNode,一般用于beforeUpateupdated钩子函数中

自定义指令分为2中,私有的和全局的

私有自定义指令

在vue3中有一个快捷方法就是V开头的函数,驼峰命名就可以默认

<template>
  <div v-red>这是a</div>
</template>
<script setup>
const vRed = (el) => {
  el.style.color = 'red';
};
</script>

或者在组件内祖册

<template>
  <div v-red>这是a</div>
</template>
<script>
export default {
  directives: {
    red: (el) => {
      el.style.color = 'red';
    }
  }
};
</script>

效果: image.png

全局自定义指令

自动聚焦例子:在src下新建directive文件夹,focus.js文件 默认导出

export default (el) => {
  el.focus()
};

在main.js引入

import focus from '@/directive/focus';

在组件中使用

createApp(App).directive('focus', focus).mount('#app');

image.png

开发中常见的自定义指令

防抖

mounted(el, binding) {
    // 至少需要回调函数以及监听事件类型
    if (typeof binding.value.fn !== 'function' || !binding.value.event) return;
    let delay = 200; // 默认延迟时间
    el.timer = null;
    el.handler = function () {
      if (el.timer) {
        clearTimeout(el.timer);
        el.timer = null;
      }
      el.timer = setTimeout(() => {
        binding.value.fn.apply(this, arguments);
        el.timer = null;
      }, binding.value.delay || delay);
    };
    el.addEventListener(binding.value.event, el.handler);
  },
  // 元素卸载前也记得清理定时器并且移除监听事件
  beforeMount(el, binding) {
    if (el.timer) {
      clearTimeout(el.timer);
      el.timer = null;
    }
    el.removeEventListener(binding.value.event, el.handler);
  }
<button v-debounce="{ fn: handleClick, event: 'click', delay: 500 }">

权限按钮

function isRole(value) {
  let queryButtonList = ['a', 'b', 'c', 'd'];
  return queryButtonList.some((item) => item == value);
}
export default (el, binding) => {
  el.parentNode.removeChild(el);
};

一键复制

const copy = {
  mounted(el, { value }) {
    el.$value = value
    el.handler = () => {
      if (!el.$value) {
        console.log('无复制内容')
        return
      }
      const textarea = document.createElement('textarea')
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      textarea.value = el.$value
      document.body.appendChild(textarea)
      textarea.select()
      const result = document.execCommand('Copy')
      if (result) {
        console.log('复制成功') // 可根据项目UI仔细设计
      }
      document.body.removeChild(textarea)
    }
    el.addEventListener('click', el.handler)
  },
  updated(el, { value }) {
    el.$value = value
  },
  beforeMount(el) {
    el.removeEventListener('click', el.handler)
  },
}
 
export default copy