【vue篇】Vue 自定义指令完全指南:从入门到高级实战

在 Vue 开发中,你是否遇到过:

"如何让输入框自动聚焦?" "如何实现图片懒加载?" "如何集成 Chart.js 到 Vue 组件?"

数据驱动 无法满足需求时,自定义指令(Custom Directives)就是你的终极武器。

本文将从 基础语法高级实战,全面解析 Vue 自定义指令的用法与原理。


一、为什么需要自定义指令?

✅ Vue 的哲学

"数据驱动视图" ------ 大部分情况下,你只需修改数据,Vue 自动更新 DOM。

❌ 但有些场景例外

场景 数据驱动不足
输入框聚焦 无数据变化
图片懒加载 需监听 scroll 事件
集成第三方库(如 DatePicker 需直接操作 DOM
按钮权限控制(v-permission) 需动态显示/隐藏

💥 这些场景需要直接操作 DOM,此时自定义指令是最佳选择。


二、基础语法:钩子函数详解

📌 钩子函数执行时机

text 复制代码
bind → inserted → update → componentUpdated → unbind
钩子 触发时机 典型用途
bind 指令第一次绑定到元素 初始化设置(如添加事件监听)
inserted 元素插入父节点 访问 DOM 尺寸、位置
update 组件 VNode 更新时 值变化时更新 DOM
componentUpdated 组件及其子组件更新后 执行依赖完整 DOM 的操作
unbind 指令解绑时 清理事件、定时器

🎯 钩子函数参数

js 复制代码
function myDirective(el, binding, vnode, prevVnode) {
  // el: 绑定的 DOM 元素
  // binding: 指令对象
  // vnode: 虚拟节点
  // prevVnode: 上一个 VNode(仅 update/componentUpdated)
}

binding 对象详解

属性 示例 说明
value v-my-dir="msg"msg 的值 指令绑定的值
oldValue 更新前的值 仅在 update/componentUpdated 中可用
arg v-my-dir:arg'arg' 传入的参数
modifiers v-my-dir.mod1.mod2{ mod1: true, mod2: true } 修饰符对象
expression v-my-dir="a + b"'a + b' 绑定的表达式字符串

三、定义方式

✅ 1. 全局指令

js 复制代码
Vue.directive('focus', {
  inserted(el) {
    el.focus();
  }
});

✅ 2. 局部指令

vue 复制代码
<template>
  <input v-focus />
</template>

<script>
export default {
  directives: {
    focus: {
      inserted(el) {
        el.focus();
      }
    }
  }
}
</script>

四、初级应用:5 个经典案例

🎯 1. 自动聚焦(v-focus

js 复制代码
Vue.directive('focus', {
  inserted(el) {
    el.focus();
  }
});
vue 复制代码
<input v-focus />

🎯 2. 点击外部关闭(v-click-outside

js 复制代码
Vue.directive('click-outside', {
  bind(el, binding) {
    const handler = (e) => {
      if (!el.contains(e.target)) {
        binding.value(e); // 执行传入的函数
      }
    };
    document.addEventListener('click', handler);
    el._clickOutside = handler;
  },
  unbind(el) {
    document.removeEventListener('click', el._clickOutside);
  }
});
vue 复制代码
<div v-click-outside="closeMenu">菜单</div>

🎯 3. 相对时间(v-timeago

js 复制代码
Vue.directive('timeago', {
  bind(el, binding) {
    const date = new Date(binding.value);
    el.textContent = `${Math.floor((Date.now() - date) / 60000)}分钟前`;
  },
  update(el, binding) {
    // 值变化时更新
    if (binding.value !== binding.oldValue) {
      const date = new Date(binding.value);
      el.textContent = `${Math.floor((Date.now() - date) / 60000)}分钟前`;
    }
  }
});
vue 复制代码
<span v-timeago="post.createdAt"></span>

🎯 4. 按钮权限(v-permission

js 复制代码
Vue.directive('permission', {
  bind(el, binding) {
    const userRoles = this.$store.getters.roles;
    if (!userRoles.includes(binding.value)) {
      el.parentNode.removeChild(el); // 移除无权限的按钮
    }
  }
});
vue 复制代码
<button v-permission="'admin'">删除</button>

🎯 5. 滚动动画(v-scroll

js 复制代码
Vue.directive('scroll', {
  inserted(el, binding) {
    const onScroll = () => {
      if (window.scrollY > 100) {
        el.classList.add('scrolled');
      } else {
        el.classList.remove('scrolled');
      }
    };
    window.addEventListener('scroll', onScroll);
    el._scrollHandler = onScroll;
  },
  unbind(el) {
    window.removeEventListener('scroll', el._scrollHandler);
  }
});
vue 复制代码
<header v-scroll></header>

五、高级应用:2 个深度实战

🚀 1. 图片懒加载(v-lazy

js 复制代码
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      imageObserver.unobserve(img);
    }
  });
});

Vue.directive('lazy', {
  bind(el, binding) {
    el.dataset.src = binding.value;
    el.classList.add('lazy');
    imageObserver.observe(el);
  },
  update(el, binding) {
    if (binding.value !== binding.oldValue) {
      el.dataset.src = binding.value;
      // 如果已进入视口,立即加载
      if (el.getBoundingClientRect().top < window.innerHeight * 1.5) {
        el.src = binding.value;
      }
    }
  },
  unbind(el) {
    imageObserver.unobserve(el);
  }
});
vue 复制代码
<img v-lazy="imageUrl" />

🚀 2. 集成 ECharts(v-chart

js 复制代码
Vue.directive('chart', {
  bind(el) {
    el._chart = echarts.init(el);
  },
  update(el, binding) {
    const chart = el._chart;
    if (binding.value) {
      chart.setOption(binding.value, true);
    }
  },
  unbind(el) {
    el._chart.dispose();
  }
});
vue 复制代码
<div v-chart="chartOption" style="width: 400px; height: 300px;"></div>

六、重要注意事项

⚠️ 1. 不要修改 v-model 绑定的值

vue 复制代码
<input v-model="msg" v-my-directive />
  • ❌ 在指令中直接 el.value = 'new'msg 不会更新;
  • ✅ 正确做法:触发 inputchange 事件。
js 复制代码
el.value = 'new';
el.dispatchEvent(new Event('input'));

⚠️ 2. 清理副作用

  • unbind 中移除事件监听;
  • 清除定时器;
  • 销毁第三方实例(如 ECharts)。

⚠️ 3. 性能优化

  • 避免在 update 中做昂贵操作;
  • 使用 binding.valuebinding.oldValue 判断是否需要更新。

💡 结语

"自定义指令是 Vue 的'最后一公里'解决方案。"

场景 推荐方案
简单 DOM 操作 自定义指令
复杂逻辑复用 Mixin / Composition API
UI 组件 普通组件
钩子 使用场景
bind 初始化
inserted 访问布局
update 值变化
unbind 清理资源

掌握自定义指令,你就能:

✅ 实现原生 DOM 操作;

✅ 集成第三方库;

✅ 创建可复用的 DOM 行为;

✅ 补充数据驱动的不足。

相关推荐
用户938515635077 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
CodeSheep8 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员
鱼樱前端8 小时前
用 AI 做内容变收入
前端·人工智能·ai编程
浮生望8 小时前
React + TypeScript 组件设计进阶:从类型约束到无状态组件的三次进化
前端
90后的晨仔8 小时前
Mac 开发 uni-app 终极方案:让安卓模拟器彻底脱离 Android Studio 独立运行
前端·vue.js
90后的晨仔9 小时前
别再搞混了!uni-app 中 node_modules 和 uni_modules 到底是什么关系?
前端
kyriewen9 小时前
面试官问"这段逻辑为什么这样写"——我才发现,这半年我写的代码,我自己都解释不了
前端·javascript·面试
谢小飞10 小时前
大文件上传很难?学会这招,GB文件秒传不是梦
前端·node.js
程序员黑豆10 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程
To_OC10 小时前
别只拿 useRef 绑 DOM 了,搭配 Web Worker 解决页面卡顿才是真的香
前端·react.js·dom