在 Vue 2 中,自定义指令允许开发者扩展 Vue 的模板语法,从而满足特定的 DOM 操作需求。以下是对自定义指令的详细说明,包括入参、出参、钩子函数等内容。官方文档:vue2自定义指令官方文档
自定义指令的定义
自定义指令分为 全局指令 和 局部指令 两种。
-
全局指令:通过
Vue.directive方法定义,适用于整个 Vue 应用。Vue.directive('directiveName', { // 钩子函数 }); -
局部指令:在组件内部通过
directives选项定义,仅适用于该组件。export default { name: 'ComponentName', directives: { directiveName: { // 钩子函数 } } };
自定义指令的钩子函数列表
Vue 的自定义指令提供了一组生命周期钩子函数,用于在指令的不同阶段执行逻辑。
| 钩子函数 | 描述 | 执行时机 |
|---|---|---|
bind(el, binding, vnode) | 指令第一次绑定到元素时调用,仅调用一次。 | 指令绑定到元素时立即调用。 |
inserted(el, binding, vnode) | 被绑定元素插入父节点时调用。 | 元素插入 DOM 后调用。 |
update(el, binding, vnode, oldVnode) | 所在组件的 VNode 更新时调用,但可能在子节点更新之前。 | 当被绑定的元素所在的模板更新时调用。 |
componentUpdated(el, binding, vnode, oldVnode) | 所在组件的 VNode 及其子节点全部更新后调用。 | 组件及子节点更新完成后调用。 |
unbind(el, binding) | 指令从元素解绑时调用,仅调用一次。 | 指令被移除时调用。 |
自定义指令钩子函数的入参
自定义指令的钩子函数接收以下参数:
el:DOM 元素,指令所绑定的元素。
id: 获取或设置元素的id属性。className: 获取或设置元素的class属性。querySelector(selector): 使用 CSS 选择器获取第一个匹配的子节点。getAttribute(name): 获取指定属性的值。setAttribute(name, value): 设置指定属性的值。style: 操作元素的内联样式。textContent: 设置或获取元素的文本内容。innerHTML: 设置或获取元素的 HTML 内容。addEventListener(eventName, eventHandler): 添加事件监听器。removeEventListener(eventName, eventHandler): 移除事件监听器。appendChild(node): 在子节点列表的末尾添加一个节点。remove():移除当前节点removeChild(childNode): 移除一个子节点children: 获取元素的所有子元素节点。childNodes: 获取元素的所有子节点(包括元素节点、文本节点等)。
document.createElement()是最常用的方法,用于创建一个新的 HTML 元素。
在自定义指令中,为什么有时候使用
document,有时候使用el进行对节点或元素的操作呢? 答:document:当需要捕捉全局事件(如 keydown、scroll 等),这些事件可能在整个文档中触发,而不仅仅是某个特定的 DOM 元素。适用于对大量子元素的事件进行统一管理,避免为每个子元素单独绑定事件el:事件仅与当前指令绑定的元素相关,如按钮的点击事件,只需要响应当前按钮的点击行为。限制事件监听的作用范围,减少性能开销
binding:对象,包含以下属性:
name:指令名。value:指令绑定的值。oldValue:前一个值,仅在update和componentUpdated中可用。expression:绑定值的字符串形式。arg:传给指令的参数。它允许你为指令传递一个参数值,通常是一个字符串。通过arg,你可以在指令中动态地指定某种行为、名称或主题等,例如:动态颜色指令v-color:blue、v-color:#fffff等modifiers:包含指令的修饰符的对象(以点开始),它可以在指令后添加额外的点语法(如 .foo 或 .bar),为指令赋予额外的语义或行为。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }备注:修饰符的值始终为 true,无法传递具体数据(如 .modifier=42 无效)。需要传递数据时,应使用指令的值(value)
示例:自定义防抖指令(全局定义)
const debounce = {
// 防抖指令:v-debounce
bind(el, binding) {
let {name,value,expression,arg,modifiers} = binding;
// 指令的名
console.log(name) // 打印的值为:debounce
// 指令绑定的值
console.log(value) // 打印的值为: ƒ handleClick() {console.log('按钮被点击了');}
// 绑定值的字符串形式
console.log(expression) // 打印的值为: handleLongPress
// 传给指令的参数
// binding.arg 的值取决于指令的语法。
// 当使用 v-debounce:click.1000="handleClick" 时,binding.arg 的值是 click,
// 而不是 click.1000。这是因为 Vue 的指令语法解析 :click 作为指令的 arg,
// 而 .1000 被视为修饰符(modifier)
console.log(arg) // 打印的值为: click
// 包含指令的修饰符的对象
console.log(modifiers) // {1000:true}
}
}
export default debounce;
使用示例:
<template>
<div>
<button v-debounce:click.1000="handleClick">点击防抖按钮</button>
</div>
</template>
<script>
export default {
name: 'DebounceExample',
methods: {
handleClick() {
console.log('按钮被点击了');
}
}
};
</script>
vnode
vnode:Vue 的虚拟节点,通过 vnode,你可以直接操作虚拟 DOM 的数据结构,从而实现复杂的渲染逻辑
tag:字符串,表示节点的类型(如"div")。data:对象,包含节点的属性、样式、事件等信息。children:数组,包含子节点的 VNode。text:字符串,表示文本节点的内容(仅在文本节点时使用)。context:对组件类型的 VNode,指向其上下文(组件实例)。componentOptions:对组件类型的 VNode,包含组件定义、props 等信息。- .......等
示例:
// 示例1:修改子节点的内容
Vue.directive('modify-child', {
bind(el, binding, vnode) {
const childNodes = vnode.child; // 获取子节点
childNodes.forEach((child) => {
if (child.data && child.data.attrs && child.data.attrs.id === 'target') {
child.data.attrs.class = 'highlight'; // 修改子节点的样式
}
});
}
});
//-----------------------------------------------------------------------------------
//示例2:条件显示内容
Vue.directive('conditional-display', {
update(el, binding, vnode) {
if (binding.value) {
// 渲染自定义内容
const content = h('div', { class: 'show' }, '显示内容');
vnode.componentInstance.$slots.default = [content];
} else {
// 不显示内容
vnode.componentInstance.$slots.default = [];
}
}
});
// ----------------------------------------------------------------------------------
// 动态插入子节点
Vue.directive('dynamic-content', {
bind(el, binding, vnode) {
const newChild = h('span', { class: 'red-text' }, '动态内容');
vnode.componentInstance.$slots.default = [newChild];
}
});
使用示例:
<div v-modify-child>
<span id="target"> 示例1:修改子节点的内容</span>
</div>
-----------------------------------------------------
<div v-conditional-display="isVisible">示例2:条件显示内容</div>
-----------------------------------------------------
<div v-dynamic-content>原始内容</div>
oldVnode:旧的虚拟节点,仅在 update 和 componentUpdated 中可用。
vnode和el的区别? el 和 vnode它们分别代表了真实 DOM 元素和虚拟 DOM 节点, el:是浏览器中实际存在的 DOM 元素节点。可以直接通过 DOM API(如 el.style、el.classList、el.addEventListener 等)对它进行操作,影响页面布局和样式。如果你需要直接操作页面上的 DOM 元素,如修改元素的样式、绑定事件或插入内容,可以使用 el vnode:是 Vue 的虚拟 DOM 数据对象,描述了 DOM 元素的结构和状态,通过操作 vnode,可以间接影响 Vue 的渲染逻辑,进而更新真实 DOM。如果需要动态生成或修改 DOM 内容(如插入新的元素、修改子节点等),可以通过 vnode 和渲染函数 h 来实现。 **
自定义指令钩子函数的出参
自定义指令的钩子函数本身没有明确的返回值,但它们可以通过以下方式影响行为:
- 直接修改 DOM:通过
el参数操作 DOM 元素,。 - 修改绑定值:通过 Vue 的响应式系统或事件机制(如
$emit)影响组件的行为。
示例:将数据传递到 Vue 组件中,通过事件($.emit)触发
Vue.directive('click-count', {
bind(el, binding, vnode) {
let count = 0;
el.addEventListener('click', () => {
count++;
vnode.componentInstance.$emit('countUpdated', count);
});
}
});
使用示例:
<template>
<div>
<button v-click-count @countUpdated="handleCountUpdated">点击增加计数</button>
</div>
</template>
<script>
export default {
methods: {
handleCountUpdated(count) {
console.log('当前计数是:', count);
}
}
};
</script>
注意事项
- 避免直接操作全局变量:自定义指令中尽量避免直接操作全局对象(如
window),尽量使用参数化的方式。 - 清理操作:在
unbind钩子中清理事件监听器或定时器,避免内存泄漏。 - 与组件生命周期协同工作:某些指令逻辑需要结合组件的生命周期方法(如
mounted或destroyed)一起使用。 - 使用场景:自定义指令适用于 DOM 操作频繁且需要复用的场景,如防抖、节流、拖放等。
通过掌握自定义指令的钩子函数和参数,可以灵活地扩展 Vue 的功能,以满足各种复杂的 UI 需求。 如有需要可进行查看项目中常用的自定义指令。