Vue.js 自定义指令
一、什么是自定义指令?
Vue 内置了v-model、v-show、v-if等指令,自定义指令就是自己造一个v-xxx,用来对 DOM 元素进行底层操作。
适用场景:需要直接操作 DOM,且逻辑可复用的情况,如自动聚焦、权限控制、拖拽、懒加载等。
二、基本用法
注册指令
// 全局注册app.directive('focus',{mounted(el){el.focus();}});// 局部注册exportdefault{directives:{focus:{mounted(el){el.focus();}}}};使用指令
<inputv-focus/>三、指令钩子函数
Vue 3 钩子
app.directive('my-dir',{// 元素被插入 DOM 前created(el,binding,vnode){},// 元素插入 DOM 后(最常用)mounted(el,binding,vnode){},// 组件更新后updated(el,binding,vnode,prevVnode){},// 元素卸载前beforeUnmount(el,binding,vnode){},// 元素卸载后unmounted(el,binding,vnode){},});Vue 2 钩子(名称不同)
Vue.directive('my-dir',{bind(el,binding,vnode){},// → 对应 Vue 3 的 mountedinserted(el,binding,vnode){},// → 对应 Vue 3 的 mountedupdate(el,binding,vnode){},// → 对应 Vue 3 的 updatedcomponentUpdated(el,binding){},// → 对应 Vue 3 的 updatedunbind(el,binding,vnode){},// → 对应 Vue 3 的 unmounted});Vue 2 vs Vue 3 钩子对照
| Vue 2 | Vue 3 |
|---|---|
bind | mounted |
inserted | mounted |
update | updated |
componentUpdated | updated |
unbind | unmounted |
四、钩子参数
mounted(el,binding,vnode){// el:指令绑定的 DOM 元素,可直接操作// binding:一个对象,包含以下属性}binding 对象
<divv-my-dir:arg.modifier="value"/>{value:...,// 指令的绑定值,如 v-my-dir="1+1" → 2oldValue:...,// 上一次的值(仅在 updated 中可用)arg:'arg',// 指令参数,如 v-my-dir:foo → 'foo'modifiers:{// 修饰符对象modifier:true// 如 v-my-dir.bold → { bold: true }},instance:...,// 组件实例dir:...,// 指令定义对象本身}五、简写形式
如果只在mounted和updated时执行相同逻辑,可以用函数简写:
// 完整写法app.directive('color',{mounted(el,binding){el.style.color=binding.value;},updated(el,binding){el.style.color=binding.value;}});// 简写(mounted + updated 都执行)app.directive('color',(el,binding)=>{el.style.color=binding.value;});六、实战示例
1. 自动聚焦 v-focus
app.directive('focus',{mounted(el){el.focus();}});<inputv-focus/><!-- 页面加载后自动聚焦 -->2. 权限控制 v-permission
app.directive('permission',{mounted(el,binding){constuserRoles=['user'];// 当前用户角色constrequiredRole=binding.value;if(!userRoles.includes(requiredRole)){el.parentNode?.removeChild(el);// 无权限则移除元素}}});<buttonv-permission="'admin'">删除用户</button><!-- 普通用户看不到这个按钮 -->3. 防抖 v-debounce
app.directive('debounce',{mounted(el,binding){lettimer=null;el.addEventListener('click',()=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>{binding.value();},500);});}});<buttonv-debounce="handleSubmit">提交</button>4. 拖拽 v-draggable
app.directive('draggable',{mounted(el){el.style.position='absolute';el.style.cursor='move';letisDragging=false;letstartX,startY,initialLeft,initialTop;el.addEventListener('mousedown',(e)=>{isDragging=true;startX=e.clientX;startY=e.clientY;initialLeft=el.offsetLeft;initialTop=el.offsetTop;});document.addEventListener('mousemove',(e)=>{if(!isDragging)return;constdx=e.clientX-startX;constdy=e.clientY-startY;el.style.left=initialLeft+dx+'px';el.style.top=initialTop+dy+'px';});document.addEventListener('mouseup',()=>{isDragging=false;});}});<divv-draggable>拖我</div>5. 图片懒加载 v-lazy
app.directive('lazy',{mounted(el,binding){constobserver=newIntersectionObserver((entries)=>{entries.forEach(entry=>{if(entry.isIntersecting){el.src=binding.value;// 进入视口时加载图片observer.unobserve(el);// 停止观察}});});observer.observe(el);}});<imgv-lazy="/images/photo.jpg"alt="photo"/>6. 复制文本 v-copy
app.directive('copy',{mounted(el,binding){el.style.cursor='pointer';el.addEventListener('click',()=>{navigator.clipboard.writeText(binding.value).then(()=>{console.log('复制成功');});});}});<spanv-copy="shareLink">点击复制链接</span>七、动态指令参数
Vue 3 支持动态指令参数:
<!-- 动态决定设置哪个 CSS 属性 --><divv-color:[cssProp]="'red'"/><!-- 等价于 --><divv-color:color="'red'"/><!-- 当 cssProp = 'color' -->app.directive('color',(el,binding)=>{el.style[binding.arg]=binding.value;// binding.arg = 'color' → el.style.color = 'red'});八、总结
| 要点 | 说明 |
|---|---|
| 什么时候用 | 需要直接操作 DOM 且逻辑可复用 |
| 怎么注册 | app.directive()全局 /directives选项局部 |
| 最常用钩子 | mounted(元素插入后操作) |
| 简写 | 函数形式 =mounted+updated |
| 核心参数 | el(DOM 元素)、binding.value(绑定值) |
| 别忘了 | 操作 DOM 后在unmounted中清理(移除事件监听等) |
