vue 自定义指令

178 阅读4分钟

在 Vue.js 中,自定义指令是一种扩展 HTML 元素的能力,允许你在元素上绑定自定义行为。通过自定义指令,你可以直接操作 DOM、添加事件监听器、修改元素样式等。下面是关于 Vue 自定义指令的详细介绍、注意点和示例。

自定义指令的基本语法

在 Vue 中,你可以使用 Vue.directive 方法来定义自定义指令。它接受两个参数:指令名称和一个对象,该对象包含指令的生命周期钩子和相关行为。

下面是自定义指令的基本语法:

javascriptCopy code
Vue.directive('directiveName', {
  // 指令的生命周期钩子和相关行为
});

指令的生命周期钩子

自定义指令可以通过一些生命周期钩子来定义其行为。以下是一些常用的指令生命周期钩子:

  • bind: 在指令第一次绑定到元素时调用。在这里可以进行初始设置。
  • inserted: 在包含指令的元素插入到父节点时调用。
  • update: 在包含指令的元素所在的组件更新时调用,但可能在其子组件更新之前调用。
  • componentUpdated: 在包含指令的元素所在的组件及其子组件更新完成后调用。
  • unbind: 在指令从元素上解绑时调用。在这里可以进行清理工作。

指令对象的属性和方法

在自定义指令的对象中,你可以定义以下属性和方法来控制指令的行为:

  • bind: 指令第一次绑定到元素时调用,只调用一次。
  • inserted: 元素插入到父节点时调用。
  • update: 组件更新时调用,可能在其子组件更新之前调用。
  • componentUpdated: 组件及其子组件更新完成后调用。
  • unbind: 指令从元素上解绑时调用,只调用一次。
  • el: 指令所绑定的元素。
  • vm: 拥有该指令的 Vue 实例。
  • arg: 传递给指令的参数。
  • modifiers: 一个包含指令修饰符的对象。
  • value: 指令的绑定值。

注意事项

在使用自定义指令时,需要注意以下几点:

  1. 自定义指令的名称必须以 v- 开头,这是为了和内置指令进行区分。
  2. 在定义指令时,可以选择性地只使用需要的生命周期钩子和属性。
  3. 自定义指令可以通过操作 DOM 元素来实现一些特定行为,但应该尽量避免直接操作 DOM,而是通过数据驱动的方式来实现。
  4. 自定义指令可以全局注册,也可以在组件内部注册。全局注册的指令可以在应用的所有组件中使用,而局部注册的指令仅限于该组件内部使用。

自定义指令的示例

下面是一个简单的示例,展示了如何自定义一个指令,在鼠标悬停在元素上时改变其背景颜色:

htmlCopy code
<template>
  <div>
    <p v-custom-directive>Hover over me</p>
  </div>
</template>

<script>
Vue.directive('custom-directive', {
  bind(el) {
    el.style.backgroundColor = 'red';
  },
  inserted(el) {
    el.style.transition = 'background-color 0.5s';
    el.addEventListener('mouseenter', function () {
      el.style.backgroundColor = 'blue';
    });
    el.addEventListener('mouseleave', function () {
      el.style.backgroundColor = 'red';
    });
  },
  unbind(el) {
    el.removeEventListener('mouseenter');
    el.removeEventListener('mouseleave');
  }
});

export default {
  // 组件的其他选项
}
</script>

在上述示例中,我们定义了一个名为 custom-directive 的自定义指令。在 bind 钩子中,我们将初始背景颜色设置为红色。在 inserted 钩子中,我们添加了鼠标进入和离开时的事件监听器,用于改变背景颜色。在 unbind 钩子中,我们移除了事件监听器。

在模板中使用指令时,我们通过 v-custom-directive 将指令绑定到 <p> 元素上。当鼠标悬停在元素上时,背景颜色将改变为蓝色,离开时恢复为红色。

这只是一个简单的示例,你可以根据需要自定义指令的行为,实现更复杂的功能。

相关自定指令

1.v-click-outside (Element-UI 中的)


//element-ui/src/utils/dom
/* istanbul ignore next */
export const on = (function() {
  if (document.addEventListener) {
    return function(element, event, handler) {
      if (element && event && handler) {
        element.addEventListener(event, handler, false);
      }
    };
  } else {
    return function(element, event, handler) {
      if (element && event && handler) {
        element.attachEvent('on' + event, handler);
      }
    };
  }
})();

import Vue from 'vue';
import { on } from 'element-ui/src/utils/dom';

const nodeList = [];
const ctx = '@@clickoutsideContext';

let startClick;
let seed = 0;

!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));

!Vue.prototype.$isServer && on(document, 'mouseup', e => {
  nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});

function createDocumentHandler(el, binding, vnode) {
  return function(mouseup = {}, mousedown = {}) {
    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[el[ctx].methodName]();
    } else {
      el[ctx].bindingFn && el[ctx].bindingFn();
    }
  };
}

/**
 * v-clickoutside
 * @desc 点击元素外面才会触发的事件
 * @example
 * ```vue
 * <div v-element-clickoutside="handleClose">
 * ```
 */
export default {
  bind(el, binding, vnode) {
    nodeList.push(el);
    const id = seed++;
    el[ctx] = {
      id,
      documentHandler: createDocumentHandler(el, binding, vnode),
      methodName: binding.expression,
      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;

    for (let i = 0; i < len; i++) {
      if (nodeList[i][ctx].id === el[ctx].id) {
        nodeList.splice(i, 1);
        break;
      }
    }
    delete el[ctx];
  }
};

2.v-copy 指令

export default {
    bind(el, binding) {
      el.clickHandler = function(event) {
        const textarea = document.createElement('textarea');
        textarea.value = binding.value;
        textarea.style.position = 'fixed';
        textarea.style.top = '-9999px';
        textarea.style.left = '-9999px';
        document.body.appendChild(textarea);
        textarea.select();
        textarea.setSelectionRange(0, textarea.value.length);

        let success = false;
        try {
          success = document.execCommand('copy');
        } catch (error) {
          console.error('Copy command failed:', error);
        }

        document.body.removeChild(textarea);

        if (success) {
          el.dispatchEvent(new Event('copied'));
          el.classList.add('copied');
          setTimeout(() => {
            el.classList.remove('copied');
          }, 1000);
        } else {
          console.error('Copy command is not supported or failed');
        }
      };

      el.addEventListener('click', el.clickHandler);
    },
    unbind(el) {
      el.removeEventListener('click', el.clickHandler);
      delete el.clickHandler;
    }
}