vue3 自定义指令

175 阅读3分钟

全局注册指令

编写指令

/src/directives/tableLoadMore.ts

import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
    copyData: string | number;
    __handleClick__: any;
}
// 表格滚动加载指令
export const tableLoadMore: Directive = {
    mounted(el: ElType, binding: DirectiveBinding) {
        if (typeof binding.value !== "function") {
            throw "callback must be a function";
        }
        if (!binding.arg) {
            throw "需要传递给指令table-class参数";
        }
        console.log(binding,'binding');
        
        
        const SELECTDROPDOWN_DOM:ElType|null = document.querySelector(`.${binding.arg} .el-scrollbar__wrap`);
        SELECTDROPDOWN_DOM?.addEventListener("scroll", function () {
            // scrollTop 这里可能因为浏览器缩放存在小数点的情况,导致了滚动到底部时
            // scrollHeight 减去滚动到底部时的scrollTop ,依然大于clientHeight 导致无法请求更多数据
            // 这里将scrollTop向上取整 保证滚到底部时,触发调用
            const CONDITION = this.scrollHeight - Math.ceil(this.scrollTop) <= this.clientHeight;
            // el.scrollTop !== 0 当输入时,如果搜索结果很少,以至于没看到滚动条,那么此时的CONDITION计算结果是true,会执行bind.value(),此时不应该执行,否则搜索结果不匹配
            if (CONDITION && this.scrollTop !== 0) {
                binding.value();
            }
        });
    },
    beforeUnmount(el: ElType) {
        el.removeEventListener("scroll", el.__handleClick__);
    }
};

权限按钮指令

/src/directives/permissionDirective.ts

import store from '@/store'
import type { Directive, DirectiveBinding } from 'vue';

const checkPermission = (el: HTMLElement, binding: DirectiveBinding) => {
  const { value } = binding
  // const roles= JSON.parse(sessionStorage.getItem('roles'))
  const roles = store.getters?.roles

  if ( value instanceof Array && value.length) {
      const elRoles = value
      const hasPermission = roles.some(role =>elRoles.includes(role))

      if (!hasPermission) {
        // el.parentNode && el.parentNode.removeChild(el))
        el.style.display = 'none'
      }
  } else {
    throw new Error(`Directive value should be a non-empty array containing roles`)
  }
}

export  const permissionDirective: Directive {
  mounted: checkPermission,
  updated: checkPermission
}

用法1: v-permission:DELETE, 指令里用binding.arg接收

用法2: v-permission="['add']", 指令里用binding.value接收

图片预览自定义指令

/src/directives/previewImageDirective.ts

import type { Directive, DirectiveBinding } from 'vue';
import {  h, render } from 'vue';
// import { ElImageViewer } from 'element-plus';
import { Image } from 'ant-design-vue';


export const previewImageDirective: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    el.style.cursor = 'pointer'; // 设置鼠标样式为指针

    const previewBox = document.createElement('div');
    previewBox.classList.add('preview-box');

    // const vnode = h(ElImageViewer, {
    //   urlList: [binding.value], // 图片地址
    //   hideOnClickModal: true, // 允许点击遮罩层关闭
    //   onClose: () => {
    //     el.style.cursor = ''; // 恢复鼠标样式
    //     document.body.removeChild(previewBox);
    //   },
    // });
    
    const vnode = h(Image.Preview, {
      visible: false, // 控制预览组件是否可见
      onVisibleChange: (visible: boolean) => {
        if (!visible) {
          el.style.cursor = ''; // 恢复鼠标样式
          document.body.removeChild(previewBox);
        }
      },
      src: binding.value, // 图片地址
    });
    
    render(vnode, previewBox); // 将 vnode 渲染成 html
    document.body.appendChild(previewBox); // 将 html 插入到 body 标签里面
  },
}

统一导出

在directives文件夹下建index.ts文件

export * from "./tableLoadMore"; // 表格滚动加载
export * from "./permissionDirective"; // 按钮权限指令
export * from "./previewImageDirective"; // 图片预览指令

全局注册

在main.ts中引入指令的文件, 在app上注册指令

// 自定义指令
import { createApp, Directive } from "vue";
import * as directives from "@/directives";

const app = createApp(App);
Object.keys(directives).forEach(key => {
  app.directive(key, (directives as { [key: string]: Directive })[key]);
});

组件内注册指令

使用了script setup

<script setup> 
// 在模板中启用 v-focus 
const vFocus = { mounted: (el) => el.focus() } 
</script> 
<template> 
    <input v-focus />
</template>

未使用script setup

<script> 
export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      /* ... */
    }
  }
}
</script> 

binding 参数

image.png

指令钩子函数

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}