5

Vue自定义指令

一、Vue自定义指令

本文主要讲述的是Vue自定义指令内容,为后续实现级联组件作铺垫。

① 指令简介

首先以常用的v-model指令的使用为例:

<input type="text" v-model.lazy="age"/>{{age}}
Vue指令主要用于需要进行一些DOM操作的时候,通过Vue指令将DOM操作进行封装
使用指令的时候分为左右两部分,等号左边是指令名(v-model)修饰符(.lazy),等号右边是指令表达式(age),指令表达式虽然是一个单纯的字符串,但是指令表达式的值却是当前组件上的同名属性对应的值,上面input输入框中显示的将是this.age的值

这里补充一下v-model指令的三个修饰符的用法:

  • .lazy修饰符,v-model指令默认监听的是input事件,即随用户的输入实时更新的,但是使用.lazy修饰符后,监听的事件则变成了change事件,也就是说触发条件变成了用户输入完成后输入框失去焦点或者用户按了Enter键之后值才会发生改变。
  • .number修饰符,当input输入框的类型为text的时候,其作用不是限制用户只能输入数字,而是将用户的输入尝试转换为数字类型之后再绑定到Vue实例上,即,能转换为数字就转换为数字,不能转换为数字的则仍然使用字符串的形式,比如:
<input type="text" v-model.number="age"/>{{age}}
当用户在输入框中输入"18a"后,最右侧显示的age的值仍然为18,因为其会将"18a"先转换为数字类型,所以值仍然为数字18;当用户输入"a18"后,最右侧显示的age值就会变为"a18",因为"a18"无法转换为数字,所以仍然显示字符串"a18"
  • .trim修饰符,会将用户的输入去除左右两边空格后再绑定到Vue实例上

② 自定义指令的创建与使用

指令其实是一个对象。如果想在Vue组件中创建自定义的指令,那么可以在组件上添加directives属性,其属性值为一个对象,然后将自定义指令注册到directives属性值上,对象的属性名指令名称,对象的属性值自定义指令对象。需要注意的是,指令名称采用的驼峰命名法,不包括v-部分,使用的时候驼峰命名大写部分转换为小写并用-隔开,如:

// 某个组件

export default {
    directives: { // 在当前组件上注册一个指令
        clickOutside: { // 指令名称为clickOutside,使用的时候使用v-click-outside
            // 指令对象
        }
    }
}
 <!--使用指令-->
 <div v-click-outside:bar.foo="close"></div>
指令对象内部主要是一些钩子函数,如: bindinsertedupdatecomponentUpdatedunbind,比较常用的是inserted,在使用了该指令的元素被插入到父元素内时候执行,当然最重要的就是传递给钩子函数处理的参数,因为指令最重要的作用就是操作DOM,所以其

① 第一个参数就是el,是绑定了指令的DOM元素对象,如果绑定指令的是一个组件,也就是说指令用在了组件上,那么el就代表这个组件渲染后的整个DOM元素对象。

② 第二个参数就是binding,是一个对象,包含了当前指令的一些属性,比如: binding.name值为click-outside,不包含v-部分,binding.expression值为close,binding.value值就是this.close的值,即当前组件上close属性对应的值,可以是一个单纯的数据,也可以是一个方法名,这里close就是一个组件上的close()方法,binding. modifiers值为{foo:true}就是当前指令的修饰符,是一个对象,如果没有修饰符则是一个空的对象{},binding.arg值为bar,是传递给指令的参数,即冒号修饰的部分

注意: 指令的参数必须写在修饰符的前面,即冒号必须在点号前

③第三个参数为vnode,即Vue编译生成的虚拟节点,这个参数非常有用,可以获取到非常多Vue实例相关的东西,vnode.context可以获取到当前vue组件实例,vnode.componentInstance,如果指令是用在某个组件上,那么componentInstance获取的就是使用了该指令的那个Vue组件实例

export default {
    directives: { // 在当前组件上注册一个指令
        clickOutside: { // 指令名称为clickOutside,使用的时候使用v-click-outside
            inserted(el, binding) {
                document.addEventListener("click", (e) => { // 给整个document添加click事件
                    if (e.target === el || el.contains(e.target)) { // 如果点击的区域是使用了该指令的DOM元素的内部,那么不做任何处理
                        return;
                    }
                    binding.value(); // 即调用close()方法
                });
            }
        }
    }
}
该指令实现的是点击了绑定指令的元素的外面后,执行指令绑定的方法

二、模拟实现v-model指令

实现一个v-my-model指令,具有v-model的大部分功能,以下指令代码并不是源码的实现,仅仅是简单模拟一下v-model指令的功能,包括v-model的双向绑定功能、lazy修饰符、number修饰符、trim修饰符。
export default {
    directives: { // 注册v-my-model到组件上
         myModel: { // 指令名称采用驼峰命名法
             inserted(el, binding, vnode) { // 在绑定元素插入父元素后执行
               el.value = binding.value; // 获取指令表达式的值作为input元素的value值
               const eventName = binding.modifiers.lazy ? "change": "input"; // 处理lazy修饰符,如果有lazy属性则换成change事件
               el.addEventListener(eventName, (e) => { // input元素监听input或者change事件
                   let result = e.target.value; // 获取用户输入
                   if (binding.modifiers.number && !Number.isNaN(parseInt(e.target.value))) { // 如果指令带有number修饰符,并且用户的输入能够转换为number
                       result = parseInt(e.target.value); // 将用户的输入转换为对应的数字
                   }
                   if (binding.modifiers.trim) { // 如果指令带有trim修饰符
                       result = result.trim(); // 去除用户输入的首尾空格
                   }
                   vnode.context[binding.expression] = result; // 将最终的结果保存到当前组件实例上即view --> model中
               });
               
               vnode.context.$watch(binding.expression, (newValue, oldValue) => { // 获取组件实例并监听其中的绑定的值变化
                   el.value = newValue; // 如果组件实例上数据发生变化,那么更新view数据
               });
             }
         }   
    }
}
v-my-model指令主要就是监听input或者change事件,同时通过监听组件实例中指定数据的变化实现数据的双向绑定。大体上实现了v-model指令的功能。

JS_Even_JS
2.6k 声望3.7k 粉丝

前端工程师