🔧 一、自定义指令的定义方式
全局注册(在所有组件中可用)
在 main.js 文件中通过 Vue.directive() 注册:
Vue.directive('指令名', {
bind(el, binding) { /* 初始化操作 */ },
inserted(el, binding) { /* 元素插入 DOM 后执行 */ },
update(el, binding) { /* 组件更新时触发 */ }
});
局部注册(仅在当前组件生效)
在组件配置的 directives 选项中定义:
export default {
directives: {
'指令名': {
inserted(el, binding) {
el.focus(); // 例如:自动聚焦输入框
}
}
}
};
⚙️ 二、指令的生命周期钩子函数
自定义指令支持以下钩子(均为可选):
钩子函数 触发时机
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。(可操作 DOM)
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。(值变化但子组件可能未更新)
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。(清理副作用如事件监听)
钩子参数说明:
指令钩子函数会被传入以下参数:
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
动态指令参数
指令的参数可以是动态的。例如,在 v-mydirective:[argument]=“value” 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:
<div id="baseexample">
<p>Scroll down the page</p>
<p v-pin="200">Stick me 200px from the top of the page</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})
new Vue({
el: '#baseexample'
})
这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
函数简写
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
🔧 一、表单交互增强
v-focus 自动聚焦
页面加载时自动聚焦输入框,提升表单体验
Vue.directive('focus', {
inserted(el) {
el.focus();
}
});
使用:<input v-focus>
v-debounce 防抖指令
防止按钮重复点击或搜索框频繁请求
Vue.directive('debounce', {
inserted(el, binding) {
let timer;
el.addEventListener('click', () => {
clearTimeout(timer);
timer = setTimeout(binding.value, 500);
});
}
});
使用:
<button v-debounce="submit">提交</button>
v-precision 数字精度控制
限制输入框小数位数(如金额输入)
Vue.directive('precision', {
bind(el, binding) {
el.addEventListener('input', () => {
const parts = el.value.split('.');
if (parts[1] && parts[1].length > binding.value) {
el.value = parts[0] + '.' + parts[1].slice(0, binding.value);
}
});
}
});
使用:<input v-precision="2">(限制2位小数)
二、权限与安全控制
v-auth 权限校验
根据用户角色隐藏无权限元素
Vue.directive('auth', {
inserted(el, binding) {
const userRoles = ['admin', 'editor']; // 实际从store获取
if (!userRoles.includes(binding.value)) {
el.parentNode.removeChild(el);
}
}
});
使用:<button v-auth="'admin'">删除</button>
v-emoji 禁止特殊字符
过滤输入框中的表情符号
Vue.directive('emoji', {
bind(el) {
el.addEventListener('input', () => {
el.value = el.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '');
});
}
});
使用:<input v-emoji>
💅 三、UI效果增强
v-highlight 文本高亮
动态设置元素背景色
Vue.directive('highlight', {
bind(el, binding) {
el.style.backgroundColor = binding.value || 'yellow';
},
update(el, binding) {
el.style.backgroundColor = binding.value; // 响应值变化
}
});
使用:<div v-highlight="'#ffe'">注意内容</div>
v-loading 加载状态
数据加载时显示旋转图标
Vue.directive('loading', {
bind(el, binding) {
const spinner = document.createElement('div');
spinner.className = 'spinner';
el.appendChild(spinner);
spinner.style.display = binding.value ? 'block' : 'none';
},
update(el, binding) {
el.querySelector('.spinner').style.display = binding.value ? 'block' : 'none';
}
});
使用:<div v-loading="isLoading"></div>
v-copy 一键复制
点击元素复制文本到剪贴板
Vue.directive('copy', {
inserted(el, binding) {
el.addEventListener('click', () => {
const textarea = document.createElement('textarea');
textarea.value = binding.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('Copy');
document.body.removeChild(textarea);
});
}
});
使用:<button v-copy="'要复制的文本'">复制</button>
🌐 四、第三方库集成
v-tooltip 悬浮提示
集成工具库实现高级提示效果
Vue.directive('tooltip', {
inserted(el, binding) {
new Tooltip(el, { content: binding.value });
},
unbind(el) {
el._tooltip.destroy(); // 销毁避免内存泄漏
}
});
使用:<button v-tooltip="'提示内容'">按钮</button>
v-scroll 滚动监听
实现滚动动画或懒加载
Vue.directive('scroll', {
inserted(el, binding) {
window.addEventListener('scroll', () => {
if (window.scrollY > binding.value) {
el.style.opacity = 1;
}
});
}
});
使用:<div v-scroll="100">滚动显示</div>
⚠️ 关键注意事项
生命周期选择
DOM操作(如聚焦、滚动)用 inserted 而非 bind(元素需已挂载)
清理副作用(事件监听、第三方实例)在 unbind 中执行
命名规范
指令名需使用 kebab-case(如 v-my-directive)
性能优化
高频指令(如滚动监听)需添加防抖逻辑
通过封装这些指令,可将通用DOM逻辑抽象为可复用模块,显著提升代码可维护性。完整项目建议采用全局注册(main.js中Vue.directive())。