Vue 3 自定义指令

核心概念

自定义指令用于封装底层 DOM 操作,主要应用场景:

  • 直接操作 DOM(聚焦、滚动等)
  • 集成第三方 DOM 库
  • 添加特殊行为(权限控制、拖拽等)

一、指令生命周期钩子(7个)

javascript 复制代码
const myDirective = {
  // 元素属性/事件监听应用前调用
  created(el, binding, vnode) {},
  
  // 元素插入父节点前调用
  beforeMount(el, binding, vnode) {},
  
  // 元素挂载后调用(最常用)
  mounted(el, binding, vnode) {},
  
  // 父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  
  // 父组件及子组件更新后调用
  updated(el, binding, vnode, prevVnode) {},
  
  // 父组件卸载前调用
  beforeUnmount(el, binding, vnode) {},
  
  // 指令解绑且父组件卸载后调用
  unmounted(el, binding, vnode) {}
}

二、钩子函数参数详解

  1. el: 指令绑定的 DOM 元素

  2. binding : 包含以下属性的对象

    javascript 复制代码
    {
      value:       当前指令值(v-my-dir="value"),
      oldValue:    旧值(仅 beforeUpdate/updated 可用),
      arg:         参数(v-my-dir:arg),
      modifiers:   修饰符对象(v-my-dir.mod1.mod2 → { mod1: true, mod2: true }),
      instance:    组件实例,
      dir:         指令定义对象
    }
  3. vnode: 当前虚拟节点

  4. prevVnode: 更新前的虚拟节点(仅 beforeUpdate/updated)


三、注册方式

1. 全局注册
javascript 复制代码
// main.js
import { createApp } from 'vue'

const app = createApp(App)
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})
2. 局部注册
javascript 复制代码
// Component.vue
export default {
  directives: {
    highlight: {
      mounted(el, binding) {
        el.style.backgroundColor = binding.value || 'yellow'
      }
    }
  }
}

四、使用示例

1. 基础用法
html 复制代码
<input v-focus>
<button v-permission="'admin'">管理员按钮</button>
2. 带参数
html 复制代码
<div v-pin:top="50">固定在顶部50px处</div>
javascript 复制代码
app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    el.style[binding.arg || 'top'] = `${binding.value}px`
  }
})
3. 带修饰符
html 复制代码
<button v-click.once="log">仅触发一次</button>
javascript 复制代码
app.directive('click', {
  mounted(el, binding) {
    const handler = binding.value
    let clicked = false
    
    el.addEventListener('click', () => {
      if (binding.modifiers.once && clicked) return
      handler()
      clicked = true
    })
  }
})

五、完整功能示例

1. 权限控制指令
javascript 复制代码
app.directive('permission', {
  mounted(el, binding) {
    const { value, modifiers } = binding
    const userRoles = ['admin', 'editor'] // 实际从store获取
    
    if (!value.some(role => userRoles.includes(role))) {
      if (modifiers.hide) {
        el.style.display = 'none'
      } else {
        el.disabled = true
        el.title = '无权限操作'
      }
    }
  }
})
html 复制代码
<button v-permission="['admin']">管理员操作</button>
<button v-permission.hide="['super-admin']">超级管理员操作</button>
2. 拖拽指令
javascript 复制代码
app.directive('drag', {
  mounted(el) {
    let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0
    
    const dragMouseDown = (e) => {
      e.preventDefault()
      pos3 = e.clientX
      pos4 = e.clientY
      document.onmouseup = closeDragElement
      document.onmousemove = elementDrag
    }
    
    const elementDrag = (e) => {
      e.preventDefault()
      pos1 = pos3 - e.clientX
      pos2 = pos4 - e.clientY
      pos3 = e.clientX
      pos4 = e.clientY
      el.style.top = (el.offsetTop - pos2) + "px"
      el.style.left = (el.offsetLeft - pos1) + "px"
    }
    
    const closeDragElement = () => {
      document.onmouseup = null
      document.onmousemove = null
    }
    
    el.onmousedown = dragMouseDown
  }
})
html 复制代码
<div v-drag style="position:absolute">可拖拽元素</div>
3. 防抖指令
javascript 复制代码
app.directive('debounce', {
  mounted(el, binding) {
    const [callback, delay = 300] = binding.value
    let timer = null
    
    el.addEventListener('click', () => {
      clearTimeout(timer)
      timer = setTimeout(() => callback(), delay)
    })
  }
})
html 复制代码
<button v-debounce="[submit, 500]">防抖提交</button>

六、最佳实践

  1. 优先使用组件:非 DOM 操作优先考虑组件

  2. 解绑事件 :在 unmounted 中移除事件监听

    javascript 复制代码
    unmounted(el) {
      el.removeEventListener('click', el._clickHandler)
    }
  3. 动态指令参数 :

    html 复制代码
    <div v-move:[direction]="100"></div>
  4. 指令复用:通过参数化实现通用指令

  5. 性能优化 :避免在 updated 中执行重操作


七、注意事项

  1. 组件可能使用 inheritAttrs: false 影响指令
  2. 指令优先级高于组件生命周期
  3. 在 SSR 中避免 DOM 操作
  4. 动态参数变化会触发 beforeUpdate/updated
相关推荐
志尊宝8 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
Southern Wind18 小时前
AIAgent——第一章:本地 Agent 与 Vue 3 流式全栈实战
前端·javascript·vue.js
ym hyd 11121 小时前
生鲜超市库存管理信息系统源码 Java+SpringBoot+Vue3 前后分离
java·开发语言·vue.js·spring boot·毕设
FYKJ_201021 小时前
express绿叶横店短剧推荐与影评分享平台34219-计算机课程设计、毕业设计
java·javascript·vue.js·spring boot·后端·课程设计·express
Eric.461 天前
毕设实战|SpringBoot+Vue 实现多个系统项目
vue.js·spring boot·课程设计
一颗小芹菜的日常1 天前
vue-crack插件是干什么的
前端·javascript·vue.js
peter67681 天前
vue学习小结
前端·vue.js·学习
web3d5201 天前
Vue3 地图白屏排查清单:容器、弹窗与 ol-viewport 重复创建
vue.js·gis
hai_android2 天前
一个公式看懂动态跨表查找:P8 + VLOOKUP
前端·javascript·vue.js
wdssmq2 天前
「折腾」Vue 简单的状态管理实践
vue.js