Skip to content

Vue 全局指令与局部指令详解

指令(Directives)是 Vue 中用于直接操作 DOM 的特殊属性,分为全局注册和局部注册两种方式,各有其适用场景和实现方法。

全局指令

定义与注册

在应用级别注册,对所有组件可用:

javascript
// main.js
import { createApp } from 'vue'

const app = createApp(App)

// 注册全局指令
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

app.mount('#app')

核心特点

特性说明
可用范围整个应用的所有组件
生命周期与应用生命周期一致
适用场景高频复用指令(如权限控制、工具类指令)
命名冲突后注册的会覆盖先注册的同名指令

典型用例

javascript
// 自动聚焦指令
app.directive('focus', {
  mounted(el) {
    el.focus()
  },
  updated(el) {
    if (el.dataset.autofocus === 'true') {
      el.focus()
    }
  }
})

// 权限控制指令
app.directive('permission', {
  beforeMount(el, binding) {
    if (!checkPermission(binding.value)) {
      el.style.display = 'none'
    }
  }
})

局部指令

定义与注册

在组件选项中定义,仅当前组件可用:

选项式 API

javascript
export default {
  directives: {
    highlight: {
      mounted(el, binding) {
        el.style.backgroundColor = binding.value || 'yellow'
      }
    }
  }
}

组合式 API

javascript
<script setup>
const vHighlight = {
  mounted(el, binding) {
    el.style.backgroundColor = binding.value || 'yellow'
  }
}
</script>

核心特点

特性说明
可用范围仅当前组件及其子组件
生命周期随组件销毁而移除
适用场景组件专用指令(如表单验证、UI特效)
命名冲突只影响当前组件

典型用例

javascript
// 输入框字数统计
const vCount = {
  mounted(el, binding) {
    const span = document.createElement('span')
    el.parentNode.insertBefore(span, el.nextSibling)
    el.addEventListener('input', () => {
      span.textContent = `${el.value.length}/${binding.value}`
    })
  }
}

// 拖拽指令
const vDrag = {
  mounted(el) {
    el.style.cursor = 'move'
    el.onmousedown = (e) => {
      // 拖拽逻辑实现
    }
  }
}

指令生命周期钩子

两种指令类型共享相同的生命周期:

钩子调用时机典型用途
created元素属性初始化后初始化非响应式设置
beforeMount元素插入DOM前获取初始DOM状态
mounted元素插入DOM后DOM操作、事件监听
beforeUpdate组件更新前获取更新前状态
updated组件更新后基于新状态的DOM操作
beforeUnmount组件卸载前清理事件监听器
unmounted组件卸载后最终清理
javascript
app.directive('example', {
  created(el, binding, vnode) {},
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // 等同于Vue 2的beforeDestroy
  unmounted() {}      // 等同于Vue 2的destroyed
})

指令参数详解

每个钩子函数接收以下参数:

javascript
{
  mounted(el, binding, vnode, prevVnode) {
    // el: 指令绑定的DOM元素
    // binding: 包含指令信息的对象
    // vnode: 当前虚拟节点
    // prevVnode: 上一个虚拟节点(仅beforeUpdate/updated)
  }
}

binding 对象结构

属性说明示例 (v-demo:foo.bar="baz")
value指令绑定的值baz
oldValue前一个值(仅update)-
arg指令参数'foo'
modifiers修饰符对象{ bar: true }
instance组件实例当前组件实例
dir指令定义对象指令配置对象

最佳实践

选择注册方式

场景推荐方式
多个组件共享的工具指令全局注册
特定组件的专用指令局部注册
第三方库提供的指令全局注册(通常由库处理)

性能优化

  1. 避免频繁DOM操作:在 updated 中检查值是否真的变化

    javascript
    updated(el, binding) {
      if (binding.value !== binding.oldValue) {
        // 执行DOM操作
      }
    }
  2. 事件监听清理:

    javascript
    mounted(el, binding) {
      const handler = () => { /*...*/ }
      el._clickHandler = handler
      el.addEventListener('click', handler)
    },
    unmounted(el) {
      el.removeEventListener('click', el._clickHandler)
    }

指令组合

将多个功能拆分为独立指令,通过修饰符控制行为:

vue
<button 
  v-tooltip="'提示内容'" 
  v-tooltip.right 
  v-tooltip.delay="300"
>按钮</button>

与组件对比

特性指令组件
主要用途底层DOM操作构建UI模块
模板能力完整模板支持
生命周期精细DOM钩子组件生命周期
复用方式功能复用UI+逻辑复用

何时用指令?

  • 需要直接操作DOM(如聚焦、拖拽)
  • 添加通用行为(如权限控制)
  • 集成第三方DOM库

经典指令实现

图片懒加载

javascript
app.directive('lazy', {
  mounted(el, binding) {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        el.src = binding.value
        observer.unobserve(el)
      }
    })
    observer.observe(el)
  }
})

使用:

html
<img v-lazy="'https://example.com/image.jpg'">

点击外部关闭

javascript
const vClickOutside = {
  mounted(el, binding) {
    el._clickOutside = (event) => {
      if (!el.contains(event.target)) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el._clickOutside)
  },
  unmounted(el) {
    document.removeEventListener('click', el._clickOutside)
  }
}

使用:

html
<div v-click-outside="closeDropdown">内容</div>
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!