• Vue3自定义指令(directive)



    前言

    此文章主要讲了vue3中自定义指令的使用,以及一些WebAPI的使用。如 ResizeObserver、IntersectionObserver API的使用。


    一、Vue3指令钩子函数

    • created 元素初始化
    • beforeMount 指令绑定到元素后调用 只调用一次
    • mounted 元素插入父级dom调用
    • beforeUpdate 元素被更新前调用
    • updated 元素被更新后调用
    • beforeUnmount 元素移除前调用
    • unmounted 元素被移除后调用

    vue2中的指令钩子函数有:

    bind、inserted、update、componentUpdated、unbind

    二、自定义指令的两种方式

    利用Directive可以创建自定义指令

    1.局部使用

    接收两个参数
    el:表示当前组件实例
    dir:表示传入的参数以及函数

    DirectiveBinding:与返回参数一致,使用来约束类型

    export interface DirectiveBinding<V = any> {
        instance: ComponentPublicInstance | null;
        value: V;
        oldValue: V | null;
        arg?: string;
        modifiers: DirectiveModifiers;
        dir: ObjectDirective<any, V>;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用如下:

    <script setup lang="ts">
    import {Directive, DirectiveBinding} from "vue";
    import A from "./A.vue";
    type Dir = {
      background:string
    }
    const vMove:Directive = {
      created(){
        console.log('------created-------')
      },
      beforeMount(){
        console.log('------beforeMount-------')
      },
      mounted(el:HTMLElement,dir:DirectiveBinding<Dir>){
        console.log('------mounted-------')
        el.style.background = dir.value.background
      },
      beforeUpdate(){
        console.log('------beforeUpdate-------')
      },
      updated(){
        console.log('------updated-------')
      },
      beforeUnmount(){
        console.log('------beforeUnmount-------')
      },
      unmounted(){
        console.log('------unmounted-------')
      }
    }
    </script>
    
    <template>
    <A v-move:aaa.smz="{background:'red'}"/>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    例子1:鉴权

    <script setup lang="ts">
    import {Directive} from "vue";
    
    // 用户id
    localStorage.setItem('userId','smz')
    // mock后台数据
    const permission = [
      'smz:shop:edit',
      // 'smz:shop:create',
      'smz:shop:delete'
    ]
    const userId = localStorage.getItem('userId') as string
    const vHasShow:Directive<HTMLElement,string> = (el,bingding)=>{
      // 当后台返回的权限列表中没有对应的权限,则将其隐藏
      if (!permission.includes(userId+':'+bingding.value)){
        // 这里感觉还要对用户在控制台的操作做一些操作,防止用户直接改样式
        // 监听用户对改元素的操作,在改变其值时进行样式添加
        el.style.display = 'none'
      }
    }
    </script>
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    例子2:拖拽

    这里使用拖拽需要改变拖拽的position,因为不改变,则修改元素位置不起作用

    static
    该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时 top, right, bottom, left 和 z-index 属性无效。

    <script setup lang="ts">
    /**
     * Element.firstElementChild:只读属性,返回对象第一个子元素,没有则返回Null
     * Element.clientX:只读属性,元素距离视口左边的距离(中心点)
     * Element.offsetLeft:只读属性,元素左上角距离视口左边的距离
     * Element.offsetWidth:元素宽度
     * Element.offsetHeight:元素高度
     * window.innerWidth:可视窗宽度
     * window.innerHeight:可视窗高度
     */
    import {Directive, DirectiveBinding} from "vue";
    
    const vDrea:Directive<any,void> = (el:HTMLElement,binding:DirectiveBinding)=>{
      let gap = 10
      let moveElement:HTMLDivElement = el.firstElementChild as HTMLDivElement
      const mouseDown = (e:MouseEvent)=>{
        console.log(window.innerHeight)
        let X = e.clientX - el.offsetLeft
        let Y = e.clientY - el.offsetTop
        const move = (e:MouseEvent)=>{
          let x = e.clientX - X
          let y = e.clientY - Y
          //超出边界判断
          if (x<=gap){
            x = 0
          }
          if (y<=gap){
            y = 0
          }
          if (x>= window.innerWidth -el.offsetWidth -gap){
            x = window.innerWidth -el.offsetWidth
          }
          if (y>= window.innerHeight - el.offsetHeight-gap){
            y = window.innerHeight - el.offsetHeight
          }
    
          el.style.left = x + 'px'
          el.style.top = y + 'px'
        }
        // 鼠标移动
        document.addEventListener('mousemove',move)
        //松开鼠标
        document.addEventListener('mouseup',()=>{
          //清除移动事件
          document.removeEventListener('mousemove',move)
        })
      }
      //鼠标按下
      moveElement.addEventListener('mousedown',mouseDown)
    }
    </script>
    
    <template>
      <div v-drea class="box">
        <div class="header"></div>
        <div>内容</div>
      </div>
    </template>
    
    <style lang="less" scoped>
    .box{
      position: fixed;
      width: 300px;
      height: 250px;
      border: solid 1px black;
      .header{
        height: 30px;
        background-color: black;
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    2.全局使用

    定义好全局指令文件,其中需要导出指令钩子函数

    /**
     * el:监听的dom元素
     * binding: 回调事件
     */
    export default {
        mounted(el,binding) {
            //将dom与回调的关系塞入map
            map.set(el,binding.value)
            //监听el元素的变化
            ob.observe(el)
        },
        unmounted(el) {
            //取消监听
            ob.unobserve(el)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在main.ts文件中添加以下代码
    挂载指令,省略‘v-’前缀

    import sizeDireect from '../src/directs/resize指令封装/sizeDireect'
    app.directive('size-ob', sizeDireect)
    
    • 1
    • 2

    使用:
    在需要监听的标签上使用命令 v-size-ob="handle",其中handle为回调函数,其中返回的参数为尺寸信息

     <div class="dir"  v-size-ob="handle">
    
    • 1

    例子1:监听宽高指令

    /**
     * @ResizeObserver 监听元素变化的API
     * @entries 元素变化的数组集合
     * @entry 每个被监听的元素 其中包含的属性有:
     *    borderBoxSize:边框盒尺寸
     *    contentBoxSize:内容盒尺寸
     *    contentRect:内容区域矩形信息 => DOMRectReadOnly {x: 0, y: 0, width: 3800, height: 3800, top: 0, …}
     *    devicePixelContentBoxSize:DPR尺寸
     *    target:哪一个元素发生变化
     */
    const ob = new ResizeObserver((entries)=>{
        for (const entry of entries) {
            // 获取dom元素的回调
            const handler = map.get(entry.target)
            //存在回调函数
            if (handler){
                // 将监听的值给回调函数
                handler({
                    width: entry.borderBoxSize[0].inlineSize,
                    height: entry.borderBoxSize[0].blockSize
                })
            }
        }
    })
    /**
     * map:存储dom与回调函数的映射关系
     * 使用WeakMap,防止内存溢出
     */
    const map = new WeakMap();
    /**
     * el:监听的dom元素
     * binding: 回调事件
     */
    export default {
        mounted(el,binding) {
            //将dom与回调的关系塞入map
            map.set(el,binding.value)
            //监听el元素的变化
            ob.observe(el)
        },
        unmounted(el) {
            //取消监听
            ob.unobserve(el)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    例子2:监听是否出现在视口

    vite提供了批量导入的方法 import.meta.glob
    eager:true表示静态导入

    let imageList: Record<string,{default: string}> = import.meta.glob('../../../assets/images/*.*',{eager:true})
    let arr = Object.values(imageList).map(v=>v.default)
    
    • 1
    • 2
    /**
     * IntersectionObserver:监听元素与视口交叉的API
     * 返回一个监听集合,集合每一项有intersectionRatio表示在视口存在的比例
     */
    export default {
        // @ts-ignore
         async mounted(el,binding){
             const def = await import('../../assets/vue.svg')
            el.src = def.default
             let ob = new IntersectionObserver((entries) => {
                if (entries[0].intersectionRatio >0){
                    el.src = binding.valueOf()
                    ob.unobserve(el)
                }
             })
            ob.observe(el)
    
        },
        unmounted(el){
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结

    此文章主要讲了vue3中自定义指令的使用,以及一些WebAPI的使用。如 ResizeObserver、IntersectionObserver API的使用

  • 相关阅读:
    QT 5.1.2中英文切换
    Kotlin 设置和获取协程名称
    QTabelWidget表格的插入、删除、更新、动态滑动条以及配合QFile进行表格内容的长期存储
    spring session 导致 HttpSessionListener 失效
    Make 详解
    wine 源码 vk3d wine-gecko wine-mono 各版本 国内下载地址 中国科技技术大学源
    有10个学生,每个学生的数据包括学号、姓名、3门课程的成绩,从键盘输人10个学
    Spring Boot : ORM 框架 JPA 与连接池 Hikari
    LeetCode——Weekly Contest 315&317
    云数据库 GaussDB(for Influx) 解密第十一期:让智能电网中时序数据处理更高效
  • 原文地址:https://blog.csdn.net/smznbhh/article/details/132832668