自定义指令
文章目录前言一、什么是自定义指令1.1 定义1.2 与组件的区别二、指令钩子2.1 Vue 3 钩子2.2 Vue 2 vs Vue 3 钩子对照2.3 钩子参数三、binding 对象3.1 常用属性3.2 示例四、注册方式4.1 全局注册4.2 局部注册script setup五、常见指令实现5.1 v-focus自动聚焦5.2 v-lazy图片懒加载5.3 v-permission权限控制5.4 v-debounce输入防抖5.5 v-click-outside点击外部关闭六、函数式指令6.1 简写形式七、面试聚焦7.1 Vue 3 指令钩子变化7.2 如何访问组件实例7.3 script setup 局部注册命名八、易混淆点九、思考与练习总结前言自定义指令用于对普通 DOM 元素进行底层操作适合处理聚焦、懒加载、权限控制等横切逻辑。本篇会讲清楚指令钩子与参数全局注册 vs 局部注册常见指令实现v-focus、v-lazy、v-permissionVue 2 与 Vue 3 指令钩子变化一、什么是自定义指令1.1 定义自定义指令用于在 DOM 元素挂载、更新、卸载时执行底层操作适合直接操作 DOM的场景。!-- 内置指令v-if、v-for、v-model -- !-- 自定义指令v-focus、v-lazy、v-permission -- input v-focus /1.2 与组件的区别对比项组件自定义指令粒度完整 UI 单元单个 DOM 元素用途复用 UI 结构复用 DOM 行为典型场景Button、Modal聚焦、懒加载、权限二、指令钩子2.1 Vue 3 钩子constmyDirective{// 元素挂载到 DOM 后调用mounted(el,binding,vnode,prevVnode){},// 元素更新后调用binding 变化时updated(el,binding,vnode,prevVnode){},// 元素卸载前调用beforeUnmount(el,binding,vnode,prevVnode){},// 元素卸载后调用unmounted(el,binding,vnode,prevVnode){}}2.2 Vue 2 vs Vue 3 钩子对照Vue 2Vue 3bindbeforeMountinsertedmountedupdatebeforeUpdatecomponentUpdatedupdatedunbindunmounted2.3 钩子参数mounted(el,binding,vnode,prevVnode){// el指令绑定的 DOM 元素// binding指令绑定信息对象// vnodeVue 虚拟节点// prevVnode更新前的虚拟节点仅 updated 有值}三、binding 对象3.1 常用属性mounted(el,binding){binding.value// 指令绑定的值如 v-focustrue 中的 truebinding.oldValue// 更新前的值仅 updated 可用binding.arg// 指令参数如 v-color:red 中的 redbinding.modifiers// 修饰符对象如 v-click.stop 中的 { stop: true }binding.instance// 当前组件实例Vue 3 无 this通过此访问binding.dir// 指令定义对象}3.2 示例!-- v-demo:foo.barbaz -- !-- arg: foo -- !-- modifiers: { bar: true } -- !-- value: baz -- input v-permission:edit[admin] / !-- arg: edit -- !-- value: [admin] --四、注册方式4.1 全局注册// main.jsimport{createApp}fromvueimportAppfrom./App.vueconstappcreateApp(App)// 注册指令app.directive(focus,{mounted(el){el.focus()}})app.mount(#app)!-- 任意组件中使用 -- input v-focus /4.2 局部注册script setupscript setup // 局部指令变量名必须以 v 开头 驼峰 const vFocus { mounted(el) { el.focus() } } /script template input v-focus / /templatescript setup // 从外部文件导入 import { vLazy } from /directives/lazy /script template img v-lazyimageUrl / /template五、常见指令实现5.1 v-focus自动聚焦constvFocus{mounted(el){el.focus()}}// 使用// input v-focus /// input v-focusshouldFocus / 条件聚焦constvFocus{mounted(el,binding){if(binding.value!false){el.focus()}}}5.2 v-lazy图片懒加载constvLazy{mounted(el,binding){constobservernewIntersectionObserver(([entry]){if(entry.isIntersecting){el.srcbinding.value observer.unobserve(el)}})observer.observe(el)},unmounted(el){// 清理 observer需在 mounted 中保存引用}}// 使用// img v-lazyimageUrl /// 完整版含占位图和清理constvLazy{mounted(el,binding){el.srcbinding.arg||placeholder.jpg// 占位图constobservernewIntersectionObserver(([entry]){if(entry.isIntersecting){el.srcbinding.value observer.unobserve(el)}})observer.observe(el)el._lazyObserverobserver},unmounted(el){el._lazyObserver?.unobserve(el)el._lazyObservernull}}5.3 v-permission权限控制constvPermission{mounted(el,binding){const{value}bindingconstuserRolesgetUserRoles()// 从 store 或 inject 获取consthasPermissionvalue.some(roleuserRoles.includes(role))if(!hasPermission){// 无权限移除元素或禁用el.parentNode?.removeChild(el)// 或 el.disabled true}}}// 使用// button v-permission[admin]删除/button// button v-permission[admin, editor]编辑/button5.4 v-debounce输入防抖constvDebounce{mounted(el,binding){constdelaybinding.arg?parseInt(binding.arg):300consthandlerbinding.value// 传入的回调函数lettimernullel._debounceHandler(e){clearTimeout(timer)timersetTimeout(()handler(e),delay)}el.addEventListener(input,el._debounceHandler)},unmounted(el){el.removeEventListener(input,el._debounceHandler)}}// 使用// input v-debounce:500onSearch /5.5 v-click-outside点击外部关闭constvClickOutside{mounted(el,binding){el._clickOutside(e){if(!el.contains(e.target)){binding.value()// 执行回调}}document.addEventListener(click,el._clickOutside)},unmounted(el){document.removeEventListener(click,el._clickOutside)}}// 使用下拉菜单点击外部关闭// div v-click-outsidecloseDropdown.../div六、函数式指令6.1 简写形式如果指令只在mounted和updated中触发相同逻辑可以简写为函数// 完整写法app.directive(color,{mounted(el,binding){el.style.colorbinding.value},updated(el,binding){el.style.colorbinding.value}})// 函数式简写mounted 和 updated 都会调用app.directive(color,(el,binding){el.style.colorbinding.value})七、面试聚焦7.1 Vue 3 指令钩子变化// Vue 2: bind, inserted, update, componentUpdated, unbind// Vue 3: beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted// 命名与组件生命周期对齐7.2 如何访问组件实例// Vue 2: 指令钩子中有 this// Vue 3: 无 this通过 binding.instance 访问mounted(el,binding){constinstancebinding.instance console.log(instance.someData)}7.3 script setup 局部注册命名// 变量名 vFocus → 模板中使用 v-focus// 变量名 vLazyLoad → 模板中使用 v-lazy-loadconstvFocus{mounted(el){el.focus()}}八、易混淆点指令无 thisVue 3 指令钩子中没有this通过binding.instance访问组件实例。binding.oldValue仅在updated钩子中可用用于对比新旧值。局部注册命名script setup中变量名必须以v开头如vFocus→v-focus。指令 vs 组件指令操作单个 DOM 元素组件是完整 UI 单元。记得清理在unmounted中移除事件监听、Observer 等避免内存泄漏。九、思考与练习1.自定义指令适合什么场景解析需要直接操作 DOM 的横切逻辑如聚焦、懒加载、权限控制、防抖、点击外部关闭等。不适合复杂 UI 复用用组件。2.binding 对象有哪些常用属性解析value绑定值、arg参数、modifiers修饰符、oldValue旧值仅 updated、instance组件实例。3.如何实现 v-focus 指令constvFocus{mounted(el){el.focus()}}4.Vue 3 指令如何访问组件实例解析通过binding.instance不再有this上下文。5.局部注册指令在 script setup 中如何命名解析变量名以v开头 驼峰如vFocus对应模板中的v-focus。总结自定义指令对 DOM 元素进行底层操作提供 mounted/updated/unmounted 钩子bindingvalue、arg、modifiers、instance 等绑定信息注册方式全局app.directive()局部vXxx变量常见实现v-focus、v-lazy、v-permission、v-debounceVue 3 变化钩子命名对齐组件生命周期无 this