• vue实战-完全掌握Vue自定义指令


    准备:自定义指令介绍

    除了核心功能默认内置的指令 (v-model 和 v-show等),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

    作为使用Vue的开发者,我们对Vue指令一定不陌生,诸如v-modelv-onv-forv-if等,同时Vue也为开发者提供了自定义指令的api,熟练的使用自定义指令可以极大的提高了我们编写代码的效率,让我们可以节省时间开心的摸鱼~

    对于Vue的自定义指令相信很多同学已经有所了解,自定义指令的具体写法这里就不细讲了,官方文档很详细。 但是不知道各位同学有没有这种感觉,就是这个技术感觉很方便,也不难,我也感觉学会了,就是不知道如何去应用。这篇文档就是为了解决一些同学的这些问题才写出来的。
    PS:这次要讲的自定义指令我们主要使用的是vue2.x的写法,不过vue3.x不过是几个钩子函数有所改变,只要理解每个钩子函数的含义,两者的用法差别并不大。

    试炼:实现v-mymodel

    我的上篇文章说到要自己实现一个v-model指令,这里使用v-myodel模拟一个简易版的,顺便再领不熟悉的同学熟悉一下自定义指令的步骤和注意事项。

    定义指令

    首先梳理思路:原生input控件与组件的实现方式需要区分,input的实现较为简单,我们先实现一下input的处理。
    首先我们先定义一个不做任何操作的指令

    Vue.directive('mymodel', {
       
            //只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
            bind(el, binding, vnode, oldVnode) {
       
            },
            //被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中),需要父节点dom时使用这个钩子
            inserted(el, binding, vnode, oldVnode) {
       
            },
            //所在组件的 VNode 更新时调用,**但是可能发生在其子 VNode 更新之前**。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
            update(el, binding, vnode, oldVnode) {
       
            },
            //指令所在组件的 VNode **及其子 VNode** 全部更新后调用。
            componentUpdated(el, binding, vnode, oldVnode) {
       
            },
            只调用一次,指令与元素解绑时调用。
            unbind(el, binding, vnode, oldVnode) {
       
            },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上面的注释中详细的说明了各个钩子函数的调用时机,因为我们是给组件上添加input事件和value绑定,因此我们在bind这个钩子函数中定义即可。所以我们把其他的先去掉,代码变成这样。

    Vue.directive('mymodel', {
       
            //只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
            bind(el, binding, vnode, oldVnode) {
        
            }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    简单说一下bind函数的几个回调参数,el是指令绑定组件对应的dombinding是我们的指令本身,包含namevalueexpressionarg等,vnode就是当前绑定组件对应的vnode结点,oldVnode就是vnode更新前的状态。

    接下来我们要做两件事:

    • 绑定input事件,同步inputvalue值到外部
    • value值绑定,监听value的变化,更新到inputvalue

    这对于input原生组件比较容易实现:

    //第一步,添加inout事件监听
    el.addEventListener('input', (e) => {
       
       //context是input所在的父组件,这一步是同步数据
       vnode.context[binding.expression] = e.target.value;
    })
    //监听绑定的变量
    vnode.context.$watch(binding.expression, (v) => {
       
         el.value = v;
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里解释一下上面的代码,vnode.context是什么呢,他就是我们指令所在组件的上下文环境,可以理解就是指令绑定的值所在的组件实例。不熟悉vnode结构的同学建议先看一下官方的文档,不过文档描述的比较简单,不是很全面,所以最好在控制台log一下vnode的对象看一下它具体的结构,这很有助于我们封装自定义指令,对理解Vue原理也很有帮助。

    我们可以通过context[binding.expression]获取v-model上到绑定的值,同样可以修改它。上面的代码中我们首先通过在添加的input事件中操作vnode.context[binding.expression] = e.target.value同步inputvalue值到外部(context),与使用@input添加事件监听效果是一样的;然后我们需要做第二件事,做value值的绑定,监听value的变化,同步值的变更到inputvalue上,我们想到我们可以使用Vue实例上的额$watch方法监听值的变化,而context就是那个Vue实例,binding.expression就是我们想要监听的属性,如果我们这样写

    参考vue实战视频讲解:进入学习

    <input v-mymodel='message'/>
    
    • 1

    那么binding.expression就是字符串'message'。所以我们想下面的代码这样监听绑定的响应式数据。

    //监听绑定的变量
    vnode.context.$watch(binding.expression, (v) => {
       
         el.value = v;
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    至此,inputv-mymodel的处理就完成了(当然input组件还有typecheckbox,radio,select等类型都需要去特别处理,这里就不再一一处理了,感兴趣的同学可以自己尝试去完善一下),但是对于非原生控件的组件,我们要特殊处理。
    因此我们完善代码如下:

    Vue.directive('mymodel', {
       
            //只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
            bind(el, binding, vnode, oldVnode) {
       
               //原生input组件的处理
               if(vnode.tag==='input'){
       
                    //第一步,添加inout事件监听
                    el.addEventListener('input', (e) => {
       
                       //context是input所在的父组件,这一步是同步数据
                       vnode.context[binding.expression] = e.target.value;
                    })
                    //监听绑定的变量
                    vnode.context.$watch(binding.expression, (v) => {
       
                         el.value = v;
                    })
               }else{
       //组件
    
               }
            }
    })
    
    • 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

    接下来我们要处理的是自定义组件的逻辑,

    //vnode的结构可以参见文档。不过我觉得最直观的方法就是直接在控制台打印处理
    let {
       
        componentInstance,
        componentOptions,
        context
    } = vnode;
    const {
       
       _props
    } = componentInstance;
    //处理model选项
    if (!componentOptions.Ctor.extendOptions.model) {
       
      componentOptions.Ctor.extendOptions.model = {
       
            value: 'value',
            event: 'input'
      }
    }
    let modelValue = componentOptions.Ctor.extendOptions.model.value
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    【从零开始的Java开发】1-4-1 Java继承:Object类、final关键字、注解
    Python潮流周刊#1:如何系统地自学Python?
    “.NET视频总结:认识框架的结构和组件,掌握开发工具的奥妙“
    SSM+便民自行车管理系统 毕业设计-附源码191633
    InnoDB - 锁(持续更新中...)
    使用 Linux 15 年后,我重新回到 Windows:感觉非常糟糕
    Unity 捕鱼游戏开发教程与源码
    Java 最常见的 208 道面试题(含答案)之一
    【Redis】Redis入门教程(介绍 下载安装 Jedis 图形化界面)
    什么是大数据架构
  • 原文地址:https://blog.csdn.net/yyds2026/article/details/127860936