【Vue3】自定义指令

除了 Vue 内置的一系列指令 (比如 v-modelv-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

1. 生命周期钩子函数

一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。

<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。

html 复制代码
<!-- App.vue -->
<template>
  <div>
    <button>切换</button>
    <!-- 
      钩子函数里面都可以接受这些值
      myParam: 自定义参数;
      myModifier: 自定义修饰符 
    -->
    <A v-move:myParam.myModifier="{ background: 'pink' }"></A>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, Directive } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
let flag = ref<boolean>(true)
const vMove: Directive = {
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted(...args: Array<any>) {
    console.log('mounted')
    console.log(args);

  },
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() {
    console.log('beforeUnmount')
  },
  unmounted() {
    console.log('unmounted')
  }
}
</script>

<style scoped lang="less"></style>
  • 0:该 div 元素。
  • 1:传入的参数等。比如 arg 参数,modifiers 自定义修饰符,dir 目录,传入的 value 值,instance 组件实例。
  • 2:当前组件的虚拟 DOM
  • 3:上一个虚拟 DOM
html 复制代码
<!-- App.vue -->
<template>
  <div>
    <button>切换</button>
    <!-- 
      钩子函数里面都可以接受这些值
      myParam: 自定义参数;
      myModifier: 自定义修饰符 
    -->
    <A v-move:myParam.myModifier="{ background: 'pink' }"></A>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, Directive, DirectiveBinding } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
let flag = ref<boolean>(true)
type Dir = {
  background: string;
}
const vMove: Directive = {
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
    console.log('mounted')
    console.log(el);
    console.log(dir);
    el.style.background = dir.value.background;
  },
  // 传入的数据发生变化(比如此时的background)时触发 beforeUpdate 和 updated
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() {
    console.log('beforeUnmount')
  },
  unmounted() {
    console.log('unmounted')
  }
}
</script>

<style scoped lang="less"></style>

2. 指令简写

html 复制代码
<!-- App.vue -->
<template>
  <div class="btns">
    <button v-has-show="123">创建</button>
    <button>编辑</button>
    <button>删除</button>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, DirectiveBinding } from 'vue';
import type { Directive } from 'vue'
const vHasShow: Directive = (el, binding) => {
  console.log(el, binding) ;
}
</script>

<style scoped lang="less">
.btns {
  button {
    margin: 10px;
  }
}
</style>

应用场景1:按钮鉴权

根据能否从 localStorage(或者后台返回) 中获取数据,来判断是否显示某个按钮。

html 复制代码
<!-- App.vue -->
<template>
  <div class="btns">
    <button v-has-show="'shop:create'">创建</button>
    <button v-has-show="'shop:edit'">编辑</button>
    <button v-has-show="'shop:delete'">删除</button>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, DirectiveBinding } from 'vue';
import type { Directive } from 'vue'
localStorage.setItem('userId', 'xiuxiu')
// mock 后台返回的数据
const permissions = [
  'xiuxiu:shop:create',
  // 'xiuxiu:shop:edit',  // 后台没有相应数据,则不显示该对应的按钮
  'xiuxiu:shop:delete'
]
const userId = localStorage.getItem('userId') as string
const vHasShow: Directive = (el, binding) => {
  if(!permissions.includes(userId + ':' + binding.value)) {
    el.style.display = 'none'
  }
}
</script>

<style scoped lang="less">
.btns {
  button {
    margin: 10px;
  }
}
</style>

应用场景2:鼠标拖拽

拖拽粉色框移动大盒子。

html 复制代码
<!-- App.vue -->
<template>
  <div v-move class="box">
    <div class="header"></div>
    <div>内容</div>
  </div>
</template>

<script setup lang="ts">
import { Directive, DirectiveBinding } from 'vue';


const vMove:Directive<any,void> = (el:HTMLElement, binding:DirectiveBinding)=> {
  let moveElement:HTMLElement = el.firstElementChild as HTMLElement;
  console.log(moveElement);
  const mouseDown = (e:MouseEvent) => {
    // 记录原始位置
    // clientX 鼠标点击位置的X轴坐标
    // clientY 鼠标点击位置的Y轴坐标
    // offsetLeft 鼠标点击的子元素距离其父元素的左边的距离
    // offsetTop 鼠标点击的子元素距离其父元素的顶部的距离
    let X = e.clientX - el.offsetLeft;
    let Y = e.clientY - el.offsetTop;
    const move = (e:MouseEvent) => {
      console.log(e);
      el.style.left = e.clientX - X + 'px';
      el.style.top = e.clientY - Y + 'px';
    }
    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', () => {
      document.removeEventListener('mousemove', move);
    })
  }
  moveElement.addEventListener('mousedown', mouseDown);
}
</script>

<style scoped lang="less">
.box {
  position: fixed;
  height: 200px;
  width: 200px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid #000;
  .header {
    height: 50px;
    width: 100%;
    background: pink;
    border-bottom: #000 1px solid;
  }
}
</style>

应用场景3:懒加载

复制代码
let imageList = import.meta.glob('./assets/images/*.*', { eager: true })
复制代码
let imageList = import.meta.glob('./assets/images/*.*')
复制代码
 // 判断图片是否在可视区
 const observer = new IntersectionObserver((e)=> {
   console.log(e[0]);
 })  
 // 监听元素
 observer.observe(el)


html 复制代码
<!-- App.vue -->
<template>
  <div>
    <div>
      <img v-lazy="item" width="400" height="500" v-for="item in arr" alt="">
    </div>
  </div>
</template>

<script setup lang="ts">
import { Directive, DirectiveBinding } from 'vue';

let imageList:Record<string,{default:string}> = import.meta.glob('./assets/images/*.*', { eager: true })
let arr = Object.values(imageList).map(item=>item.default)
console.log(arr);
let vLazy:Directive<HTMLImageElement,string> = async (el,binding)=> {
  const def = await import('./assets/pinia.svg')
  el.src = def.default
  // 判断图片是否在可视区
  const observer = new IntersectionObserver((e)=> {
    console.log(e[0],binding.value);
    if(e[0].intersectionRatio > 0) {
      setTimeout(()=> {
        el.src = binding.value
      },2000)
      observer.unobserve(el)
    }
  })  
  // 监听元素
  observer.observe(el)
}
</script>

<style scoped lang="less"></style>
  1. 进入可视区比例 > 0:

  2. 过 2s ,替换图片

相关推荐
我命由我1234510 分钟前
React - state、state 的简写方式、props、props 的简写方式、类式组件中的构造器与 props、函数式组件使用 props
前端·javascript·react.js·前端框架·html·html5·js
钰衡大师11 分钟前
Vue 3 源码学习教程
前端·vue.js·学习
C澒12 分钟前
React + TypeScript 编码规范|统一标准 & 高效维护
前端·react.js·typescript·团队开发·代码规范
时光少年29 分钟前
Android 视频分屏性能优化——GLContext共享
前端
IT_陈寒1 小时前
JavaScript开发者必知的5个性能杀手,你踩了几个坑?
前端·人工智能·后端
跟着珅聪学java1 小时前
Electron 精美菜单设计
运维·前端·数据库
日光倾1 小时前
【Vue.js 入门笔记】闭包和对象引用
前端·vue.js·笔记
EstherNi1 小时前
左右两侧定位的效果,vue3
javascript·vue.js
一只程序熊1 小时前
UniappX 未找到 “video“ 组件,已自动当做 “view“ 组件处理。请确保代码正确,或重新生成自定义基座后再试。
前端
林小帅1 小时前
【笔记】xxx 技术分享文档模板
前端