Vue-自定义指令directive

213 阅读4分钟

对普通DOM元素进行底层操作,根据实际情况自定义指令

全局注册:<vue2>  Vue.directive( '自定义指令名称', 对象数据/指令函数 )  

                   <vue3> const app =createApp({}); app.directive( '自定义指令名称', 对象数据/指令函数 )

局部注册:<vue2>  在Vue实例中添加directives对象数据进行注册  directives: { 指令名称:{  } }

                   <vue3> 同上

Vue2

指令定义对象提供的钩子函数 (Vue2)含义
1、bind只调用一次,在指令第一次绑定到元素时调用,可作为一次性初始化设置。
2、inserted被绑定元素插入父节点时调用 (仅保证父节点存在)
3、update所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前
4、componentUpdated指令所在组件的VNode及其子VNode全部更新后调用
5、unbind只调用一次,在指令与元素解绑时调用

Vue3

directive1.png

钩子函数提供的参数(除了el外,其余不建议修改)

1、el: 指令所绑定的元素,可以用来直接操作DOM

2、binding: 一个对象,提供的属性有:

属性名代表含义举例
name指令名,不包含前缀
value指令的绑定值v-my-directive="1+1"这里value=2
oldValue指令绑定的前一个值v-m
expression字符串形式的指令表达式v-my-directive="1+1"这里expression: "1+1"
arg传给指令的参数v-my-directive:foo这里arg为"foo"或者是写成 v-my-directive: [arg]="value"这里指令的参数就会基于组件的arg数据属性响应式更新
modifiers包含修饰符的对象v-my-directive.foo.bar="bb"这里modifiers为 { foo: true, bar: true }value为bb的值arg为'foo'oldValue:为上一次更新bb的值

3、vnode: Vue编译生成的虚拟节点

4、oldVnode:上一个虚拟节点

click-outside.js源码学习:

const nodeList = []; // 元素搜集器,会将页面中所有绑定了clickoutside指令的dom元素存储起来 
const ctx = '@@clickoutsideContext'; // 定义了命名空间 
let seed = 0; 

export default { 
      // 指令绑定时触发 
      bind(el, binding, vnode) { 
           // 每次绑定时会把dom元素存放到 nodeList 中 
           nodeList.push(el); 
           // 创建递增id标识 
           const id = seed++; 
           // 在dom元素上设置一些属性和方法 
           // ctx的作用是一个标识,为了不和原生的属性冲突 
           el[ctx] = { 
           id, 
           // 这个是点击元素区域外时会执行的函数,后面会提到 
           documentHandler: createDocumentHandler(el, binding, vnode), 
           // 绑定的值表达式,值相当于上面例子中的 "handler" 字符串 
           methodName:binding.expression, 
           // 绑定的值,值相当于上面例子中的 handler 函数 
           bindingFn: binding.value 
       }; 
}, 

       // 组件更新时触发 
       update(el, binding, vnode) { 
           el[ctx].documentHandler = createDocumentHandler(el, binding, vnode); 
           el[ctx].methodName = binding.expression; 
           el[ctx].bindingFn = binding.value; 
       }, 
           
       // 指令解绑时触发 
       unbind(el) { 
           let len = nodeList.length; 
           // 找到对应的dom元素,从 nodeList 移除它 
           for (let i = 0; i < len; i++) { 
               if (nodeList[i][ctx].id === el[ctx].id) { 
                  nodeList.splice(i, 1); break; 
               } 
           } 
           // 移除之前添加的自定义属性 
           delete el[ctx]; 
          } 
}; 
        
// 以下是对document鼠标事件进行监听 
let startClick; 

// 鼠标按下时 记录按下元素的事件对象 其中Vue.prototype.$isServer用于判断Vue实例是否运行于服务器 
!Vue.prototype.$isServer && on(document, 'mousedown', e => 
(startClick = e)); 

// 鼠标松开时 遍历 nodeList 中的元素,执行 documentHandler 
!Vue.prototype.$isServer && on(document, 'mouseup', e => {        
nodeList.forEach(node => node[ctx].documentHandler(e, startClick)); }); 

function createDocumentHandler(el, binding, vnode) { 
     // 接收参数为:鼠标松开和鼠标按下的事件对象 
     return function(mouseup = {}, mousedown = {}) { 
            // 这里一系列的判断点击区域是否在元素内,如果在区域内则跳出 
            // 1、判断vnode和vnode.context目标是否存在,vnode.context指vnode所在的上下文环境,也就是当前vnode的父节点上下文环境 2、判断mouseup.target和mousedow.target目标是否存在 
            // 3、el.contains(mouseup.target)、el.contains(mousedown.target)、el === mouseup.target判断当前目标节点是否包含鼠标按下时所在的节点,或者是否包含鼠标松开时所在的节点,或者是等于鼠标松开时所在的节点 
            // 4、vnode.context.popperElm、vnode.context.popperElm.contains(mouseup.target)、vnode.context.popperElm.contains(mousedown.target) 判断虚拟节点vnode是否存在悬浮的组件上,是否包含鼠标松开或者是鼠标按下所在节点,就比如下拉菜单上 
            if (!vnode 
            || !vnode.context 
            || !mouseup.target 
            || !mousedown.target 
            || el.contains(mouseup.target) 
            || el.contains(mousedown.target) 
            || el === mouseup.target 
            || (vnode.context.popperElm &&    
            (vnode.context.popperElm.contains(mouseup.target) ||   
            vnode.context.popperElm.contains(mousedown.target))))   
            return; 
            
            // 执行我们绑定指令时的函数 
            if (binding.expression && 
                el[ctx].methodName && 
                vnode.context[el[ctx].methodName]) { 
               // vnode.context 是组件实例上下文 
               // 就像开头的例子,methodName 是 "handler",通过索引上下文的属性找到 methods 中定义的 handler 函数 
                vnode.context[el[ctx].methodName](); 
            } else { 
                el[ctx].bindingFn && el[ctx].bindingFn(); 
            } 
         }; 
}