vue2自定义指令参数详解

1,119 阅读6分钟

在 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:前一个值,仅在 updatecomponentUpdated 中可用。
  • expression:绑定值的字符串形式。
  • arg:传给指令的参数。它允许你为指令传递一个参数值,通常是一个字符串。通过 arg,你可以在指令中动态地指定某种行为、名称或主题等,例如:动态颜色指令 v-color:bluev-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:旧的虚拟节点,仅在 updatecomponentUpdated 中可用。

vnodeel的区别? 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>

注意事项

  1. 避免直接操作全局变量:自定义指令中尽量避免直接操作全局对象(如 window),尽量使用参数化的方式。
  2. 清理操作:在 unbind 钩子中清理事件监听器或定时器,避免内存泄漏。
  3. 与组件生命周期协同工作:某些指令逻辑需要结合组件的生命周期方法(如 mounteddestroyed)一起使用。
  4. 使用场景:自定义指令适用于 DOM 操作频繁且需要复用的场景,如防抖、节流、拖放等。

通过掌握自定义指令的钩子函数和参数,可以灵活地扩展 Vue 的功能,以满足各种复杂的 UI 需求。 如有需要可进行查看项目中常用的自定义指令