在 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
不会更新; - ✅ 正确做法:触发
input
或change
事件。
js
el.value = 'new';
el.dispatchEvent(new Event('input'));
⚠️ 2. 清理副作用
- 在
unbind
中移除事件监听; - 清除定时器;
- 销毁第三方实例(如 ECharts)。
⚠️ 3. 性能优化
- 避免在
update
中做昂贵操作; - 使用
binding.value
和binding.oldValue
判断是否需要更新。
💡 结语
"自定义指令是 Vue 的'最后一公里'解决方案。"
场景 | 推荐方案 |
---|---|
简单 DOM 操作 | 自定义指令 |
复杂逻辑复用 | Mixin / Composition API |
UI 组件 | 普通组件 |
钩子 | 使用场景 |
---|---|
bind |
初始化 |
inserted |
访问布局 |
update |
值变化 |
unbind |
清理资源 |
掌握自定义指令,你就能:
✅ 实现原生 DOM 操作;
✅ 集成第三方库;
✅ 创建可复用的 DOM 行为;
✅ 补充数据驱动的不足。