对普通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
钩子函数提供的参数(除了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();
}
};
}