• Vue自定义指令



    1. 概述

    除了核心功能默认内置的指令,Vue也允许注册自定义指令。有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作。

    自定义指令分为:

    全局指令和局部指令,当全局指令和局部指令同名时以局部指令为准。

    局部指令:只对当前实例(或组件)生效

    全局指令:对全部实例(或组件)都生效

    2. 钩子函数

    自定义指令常用钩子函数:

    1. bind 第一次绑定到元素时调用(初始化)
    2. inserted 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
    3. update 数据更新时调用
    4. componentUpdated 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    5. unbind 只调用一次,指令与元素解绑时调用。

    指令的钩子会传递以下几种参数:

    • el:指令绑定到的元素。这可以用于直接操作 DOM。
    • binding:一个对象,包含以下属性。
      • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
      • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
      • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
      • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
      • instance:使用该指令的组件实例。
      • dir:指令的定义对象。
    • vnode:代表绑定元素的底层 VNode。
    • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdateupdated 钩子中可用。

    钩子函数可以理解为一个类,类中的构造函数绑定了5个函数(即钩子函数),当我们自定义钩子函数时,就会初始化这个类,然后让我们的相关代码按顺序执行这5个函数。

    有关钩子函数更详细的解释,下面这篇文章中的前两个教程写得很通俗易懂,特此推荐:

    https://blog.csdn.net/weixin_39345003/article/details/80813765

    钩子函数的执行顺序:

    <div id="app1">
        <div v-red v-if="isShow">
            <input type="text" v-model="title">
        div>
    div>
    
    <script>
        Vue.directive('red', {
            // bind 第一次绑定到元素时调用
            bind(el, bindings) {
                console.log('bind');
            },
            // inserted
            inserted(el, bindings) {
                console.log('inserted');
            },
            // update
            update(el, bindings) {
                console.log('update');
            },
            // componentUpdate
            componentUpdated(el, bindings) {
                console.log('componentUpdated');
            },
            // unbind
            unbind(el, bindings) {
                console.log('unbind');
            },
        }) 
    
        const vm1 = new Vue({
            el: '#app1',
            data: {
                isShow: true,
                title: '钩子函数执行顺序'
            }
        })
    script>
    
    • 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

    程序一执行,当数据源中的数据第一次绑定了元素就会执行bind函数,当绑定元素插入到父元素中,即显示到视图中时,会执行inserted函数:

    在这里插入图片描述

    当我们改变视图,使得数据发生改变时,就会执行updatecomponentUpdated函数:

    在这里插入图片描述

    当我们销毁被绑定元素时,即被绑定元素和数据源解绑,就会触发unbind函数:

    在这里插入图片描述

    在这里插入图片描述

    3. 自定义全局指令

    描述:

    全局定义的指令,所有的组件或vue的实例都会生效。

    语法:

    Vue.directive('指令名称,不需要写v-开头',对象或函数)

    Vue.directive('test',{
    	bind(el,bind){
    		console.log(el)
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    案例:

    <div id="app1">
        <div v-red>{{title}}div>
    div>
    
    <div id="app2">
        <div v-red>标题2div>
    div> 
    
    <script>
    	//自定义全局指令
        Vue.directive('red', {
            // bind 第一次绑定到元素时调用
            bind(el, bindings) {
                el.style.cssText = `color:red;font-size:30px`
            }
    
        }) 
    
        const vm1 = new Vue({
            el: '#app1',
            data: {
                isShow: true,
                title: '标题1'
            }
        })
    
        const vm2 = new Vue({
            el: '#app2',
            data: {
            }
        })
    script>
    
    • 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

    在这里插入图片描述

    4. 自定义局部指令

    描述:

    定义局部指令,只有当前的实例能用。

    语法:

    new Vue({
    	directives: {
    		test:{
    			bind(el,bind){}
    		},
    		// bind/update
    		test2(el,bind){}
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    案例:

    <div id="app1">
        <div v-red>{{title}}div>
    div>
    
    <div id="app2">
        <div v-red>标题2div>
    div> 
    
    <script>
        const vm1 = new Vue({
            el: '#app1',
            data: {
                isShow: true,
                title: '标题1'
            },
            // 定义局部指令,只有当前的实例能用
            directives: {
                red: {
                    // bind它还没有绑定到父元素中,初始化
                    bind(el) {
                        el.style.cssText = `color:red;font-size:30px`
                    }
                }
            }
        })
    
        const vm2 = new Vue({
            el: '#app2',
            data: {
            },
            directives: {
                red: {
                    bind(el) {
                        el.style.cssText = `color:blue;font-size:30px`
                    }
                }
            }
        })
    script>
    
    • 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

    在这里插入图片描述

    利用自定义局部指令操作Dom:

    <div id="app1">
        <div v-red>{{title}}div>
    div>
    
    <div id="app2">
        <div v-red>标题2div>
    div> 
    
    <script>
        const vm1 = new Vue({
            el: '#app1',
            data: {
                isShow: true,
                title: '标题1'
            },
            directives: {
                red: {
                    // bind表示被绑定元素还没有插入到父元素中
                    bind(el) {
                        el.style.cssText = `color:red;font-size:30px`
                        const divDom = document.createElement('div')
                        divDom.innerHTML = '我是标题1的孩子'
                        el.appendChild(divDom)
                    },
                    // 这时被绑定元素已经插入到父元素中去了,所以可以打印出被绑定元元素的父节点
                    inserted(el) {
                        console.log(el.parentNode);
                    }
                }
            }
        })
    
        const vm2 = new Vue({
            el: '#app2',
            data: {
            },
            directives: {
                red: {
                    bind(el) {
                        el.style.cssText = `color:blue;font-size:30px`
                    }
                }
            }
        })
    script>
    
    • 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

    在这里插入图片描述

    注意:在上面的代码中,我们在bind函数中,不能获取当前被绑定元素的父节点,因为此时被绑定元素刚刚初始化,还没有插入到父节点当中。在inserted函数中才能获取被绑定元素的父节点,因为此时元素已经插入到父节点当中去了。

    5. 使用自定义指令实现权限管理

    目标:

    根据地址栏中的数据,决定是否显示 button 按钮。如果地址栏中的 username 的值是 admin 时,就显示 button 按钮,否则不显示。

    注意:目标中的显示与不显示,是取决于该 Dom 元素(button 按钮)是否存在,而不是通过 css 来进行显示与隐藏。

    代码:

    <div id="app1">
        <button v-auth>查看工资button>
    div>
    
    <script>
        const vm1 = new Vue({
            el: '#app1',
            data: {
                isShow: true,
                title: '标题1'
            },
            directives: {
                auth: {
                    // 注意:是否删除按钮的操作,必须在被绑定元素(即当前元素)已经插入父节点(插入到视图)当中后进行
                    inserted(el) {
                        // 这是一种比较粗暴的写法,可维护性较低
                        // if (location.search != '?username=admin') {
                        //   el.remove()
                        // }
    
                        // URLSearchParams它是html5提供的新的Api方法,用于获取url地址中的search转为对象
                        let urlSearch = new URLSearchParams(location.search)
                        // 这样的写法可维护性较高,假如显示元素的权限还需要给到 张三 用户,则直接修改判断条件中的表达式即可
                        if (urlSearch.get('username') != 'admin') {
                            // 以前兼容性更好的写法,但是现在可以不管
                            // el.parentNode.removeChild(el)
                            el.remove()
                        }
                    }
                }
            }
        })
    script>
    
    • 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

    在这里插入图片描述

    在这里插入图片描述

    6. 使用自定义指令实现表单验证

    首先我们先完成验证手机号的功能。

    目标:

    在初始化(在 bind 函数中进行)和更新数据(在 update 函数中进行)时都要进行手机号的验证。

    思路:

    先获取手机号(收集数据),再用正则表达式判断输入框中的手机号是否合法,如果合法则手机号显示黑色,如果不合法则手机号显示红色。

    代码:

    <div id="app">
        <div>
            <input type="text" v-phone="phone" v-model="phone">
        div>
    div>
    
    <script>  
        Vue.directive('phone', {
            // 方法1:直接通过dom来完成数据的收集
            // update(el) {
            //   console.log(el.value);
            // }
    
            // 方法2:可以通过传值的方式(钩子函数)完成数据收集
            bind(el, { value }) {
                // 手机号码
                let reg = /^1[3-9]\d{9}$/
                if (!reg.test(value)) {
                    // 不合法
                    el.style.color = 'red'
                } else {
                    el.style.color = 'black'
                }
            },
            update(el, { value }) {
                // 手机号码
                let reg = /^1[3-9]\d{9}$/
                if (!reg.test(value)) {
                    // 不合法
                    el.style.color = 'red'
                } else {
                    el.style.color = 'black'
                }
            }
        }) 
    
        const vm1 = new Vue({
            el: '#app',
            data: {
                phone: '13525125121',
            }
        })
    script>
    
    • 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

    在这里插入图片描述

    在上面代码中,通过钩子函数完成数据获取与验证,代码重复率高,所以在钩子函数部分,我们可以简写成下面这种方式:

    Vue.directive('phone', (el, { value }) => {
        // 手机号码
        let reg = /^1[3-9]\d{9}$/
        if (!reg.test(phone)) {
            // 不合法
            el.style.color = 'red'
        } else {
            el.style.color = 'black'
        }
    }) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意:简写方式就是自定义指令语法中,第二个参数是函数的写法。自定义指令的简写,指的是将 bind 函数和 update 函数封装起来的写法。

    在上面的基础上,我们再加上验证错误信息的显示:

    DOCTYPE html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>vue学习使用title>
            <script src="./js/vue.js">script>
        head>
    
        <body>
            <div id="app">
                <div>
                    <input type="text" v-phone="phone" v-model="phone">
                div>
            div>
    
            <script>
                Vue.directive('phone', (el, { value }) => {
                    // 手机号码
                    let reg = /^1[3-9]\d{9}$/
                    if (!reg.test(value)) {
                        // 不合法
                        el.style.color = 'red'
                        // 没有 span 标签时,就创建 span 标签
                        if (!el.nextSibling) {
                            const spanDom = document.createElement('span')
                            spanDom.innerHTML = '不合法,修改一下'
                            // 这里是防止初始化时数据就不合法,导致被绑定元素不能成功插入到父结点中
                            // el.parentNode?.appendChild(spanDom)
                            // 上面的写法要求的浏览器版本较高,下面的写法兼容性更好
                            el.parentNode && el.parentNode.appendChild(spanDom)
                        }
                    } else {// 输入正确时,移除 span 标签
                        el.style.color = 'black'
                        el.nextSibling && el.nextSibling.remove()
                    }
                })
    
                const vm1 = new Vue({
                    el: '#app',
                    data: {
                        phone: '13525125121',
                        phoneMsg: ''
                    }
                })
            script>
    
        body>
    
    html>
    
    • 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

    在这里插入图片描述

    最后,我们使用模块化的的思路,将案例完善一下:

    DOCTYPE html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>vue学习使用title>
            <script src="./js/vue.js">script>
        head>
    
        <body>
            <div id="app">
                <div>
                    
                    
                    <input type="text" v-validate.phone v-model="phone">
                div>
                <div>
                    <input type="text" v-validate.email v-model="email">
                div>
                <div>
                    
                    <input type="text" v-validate.str="{len:3,msg:'长度过长'}" v-model="str">
                div>
            div>
    
            <script>
                // 在对象里存方法
                const validateMethod = {
                    phone(el) {
                        // 手机号码
                        let reg = /^1[3-9]\d{9}$/
                        if (!reg.test(el.value)) {
                            // 不合法
                            el.style.color = 'red'
                            if (!el.nextSibling) {
                                const spanDom = document.createElement('span')
                                spanDom.innerHTML = '不合法,修改一下'
                                // el.parentNode?.appendChild(spanDom)
                                el.parentNode && el.parentNode.appendChild(spanDom)
                            }
                        } else {
                            el.style.color = 'black'
                            el.nextSibling && el.nextSibling.remove()
                        }
                        console.log('phone');
                    },
    
                    email(el, value) {
                        console.log('email')
                    },
                    str(el, value) {
                        // 这里这个判断是容错处理,也可以不写。因为我们上面的代码中给 value传值了
                        // 如果当前封装好的代码(当作组件)给别人使用的话,使用者可能不穿值
                        if (value) {
                            if (el.value.length > value.len) {
                                if (!el.nextSibling) {
                                    const spanDom = document.createElement('span')
                                    spanDom.innerHTML = value.msg
                                    // 防止初始化数据不合法,父节点不存在
                                    el.parentNode?.appendChild(spanDom)
                                }
                            } else {
                                el.nextSibling?.remove()
                            }
                        }
                    }
                }
    
                Vue.directive('validate', (el, { value, modifiers }) => {
                    // modifiers:一个包含修饰符的对象 (如果有的话)。
                    // console.log(Object.keys(modifiers))===phone
                    Object.keys(modifiers).forEach(name => {
                        // 调用对象中的方法
                        validateMethod[name](el, value)
                    })
                })
    
                const vm1 = new Vue({
                    el: '#app',
                    data: {
                        phone: '13525125121',
                        email: 'aa@aa.com',
                        str: 'aaa'
                    },
                    methods: {
                    }
                })
            script>
    
        body>
    
    html>
    
    • 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
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    在这里插入图片描述

    注意:

    v-validate="phone"的 value 值是数据源中的 phone,v-validate.phone的 value 值是 undefined,v-validate.str="{len:3,msg:'长度过长'}"的 value 值是{len:3,msg:'长度过长'}

  • 相关阅读:
    含文档+PPT+源码等]精品基于ssm的足球联赛管理系统的设计与实现vue[包运行成功]计算机Java毕业设计SSM项目源码
    黑胶歌曲没权限,还好我会Python,一分钟一个歌单,硬盘有点不够用了~
    后台管理登录权限怎么实现的,token具体有什么作用
    第十章《日期与时间》第2节:Calendar类的使用
    Java集合篇:Map接口、Map接口的实现类、Collections集合工具类
    Anchor DETR
    【ubuntu】详解解压命令(.tar、.gz、.tar.gz、.bz2、.tar.bz2、.zip、.rar等)
    vue中三种for循环与高阶函数(filter、map、reduce)的使用
    HTTP+ 加密 + 认证 + 完整性保护 =HTTPS(HTTPS 安全通信机制)
    Arthas 介绍以及入门教程
  • 原文地址:https://blog.csdn.net/weixin_45605541/article/details/126693166