自定义指令

什么是自定义指令?

自定义指令是 Vue3 中的一个高级特性,允许我们扩展 HTML 元素的功能,通过自定义属性来操作 DOM。自定义指令可以用于添加事件监听器、修改元素样式、操作 DOM 等。

1. 指令的生命周期钩子

自定义指令有以下生命周期钩子:

  • created:指令绑定到元素时调用
  • beforeMount:元素挂载到 DOM 前调用
  • mounted:元素挂载到 DOM 后调用
  • beforeUpdate:元素更新前调用
  • updated:元素更新后调用
  • beforeUnmount:元素卸载前调用
  • unmounted:元素卸载后调用

2. 全局自定义指令

全局自定义指令可以在整个应用中使用,通过 app.directive() 方法注册。

示例:创建全局自定义指令

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

const app = createApp(App)

// 注册全局自定义指令
app.directive('focus', {
  // 元素挂载到 DOM 后自动聚焦
  mounted(el) {
    el.focus()
  }
})

// 注册带生命周期的指令
app.directive('highlight', {
  created(el, binding) {
    console.log('指令创建')
  },
  mounted(el, binding) {
    // 绑定值可以是字符串、对象或表达式
    el.style.backgroundColor = binding.value || 'yellow'
    el.style.color = binding.arg || 'black'
    
    // 修饰符
    if (binding.modifiers.bold) {
      el.style.fontWeight = 'bold'
    }
    if (binding.modifiers.italic) {
      el.style.fontStyle = 'italic'
    }
  },
  updated(el, binding) {
    // 当绑定值变化时更新
    el.style.backgroundColor = binding.value || 'yellow'
  }
})

app.mount('#app')

使用全局自定义指令

<template>
  <div>
    <!-- 基本用法 -->
    <input v-focus placeholder="自动聚焦" />
    
    <!-- 带绑定值 -->
    <p v-highlight="'lightblue'">蓝色背景</p>
    
    <!-- 带参数和修饰符 -->
    <p v-highlight:red.bold.italic="'pink'">红色文字,粉色背景,粗体斜体</p>
    
    <!-- 动态绑定值 -->
    <p v-highlight="dynamicColor">动态背景色</p>
    <button @click="dynamicColor = 'green'">变为绿色</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const dynamicColor = ref('yellow');
</script>

3. 局部自定义指令

局部自定义指令只能在当前组件中使用,通过 directives 选项注册。

示例:创建局部自定义指令

<template>
  <div>
    <input v-focus placeholder="自动聚焦" />
    <p v-tooltip="'这是一个提示'">鼠标悬停查看提示</p>
  </div>
</template>

<script setup>
// 局部自定义指令
const vFocus = {
  mounted(el) {
    el.focus()
  }
}

const vTooltip = {
  mounted(el, binding) {
    el.addEventListener('mouseenter', () => {
      // 创建提示元素
      const tooltip = document.createElement('div')
      tooltip.className = 'tooltip'
      tooltip.textContent = binding.value
      tooltip.style.position = 'absolute'
      tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'
      tooltip.style.color = 'white'
      tooltip.style.padding = '5px 10px'
      tooltip.style.borderRadius = '4px'
      tooltip.style.zIndex = '1000'
      
      // 计算位置
      const rect = el.getBoundingClientRect()
      tooltip.style.left = `${rect.left + window.scrollX}px`
      tooltip.style.top = `${rect.top + window.scrollY - 30}px`
      
      // 添加到文档
      document.body.appendChild(tooltip)
      
      // 保存到元素上,以便后续移除
      el._tooltip = tooltip
    })
    
    el.addEventListener('mouseleave', () => {
      // 移除提示元素
      if (el._tooltip) {
        document.body.removeChild(el._tooltip)
        el._tooltip = null
      }
    })
  },
  unmounted(el) {
    // 清理
    if (el._tooltip) {
      document.body.removeChild(el._tooltip)
    }
  }
}

// 导出指令
defineExpose({
  vFocus,
  vTooltip
})
</script>

4. 函数式自定义指令

对于简单的指令,可以使用函数式写法,它会在 mounted 和 updated 钩子中执行。

示例:函数式自定义指令

// 全局函数式指令
app.directive('color', (el, binding) => {
  el.style.color = binding.value
})

// 局部函数式指令
const vColor = (el, binding) => {
  el.style.color = binding.value
}

5. 指令的参数和修饰符

自定义指令可以接收参数和修饰符,通过 binding 对象访问。

示例:使用参数和修饰符

<template>
  <div>
    <!-- 参数:arg -->
    <p v-position:top="'100px'">顶部定位</p>
    
    <!-- 修饰符:modifiers -->
    <p v-position:bottom.fixed="'50px'">底部固定定位</p>
  </div>
</template>

<script setup>
const vPosition = {
  mounted(el, binding) {
    const position = binding.modifiers.fixed ? 'fixed' : 'absolute'
    el.style.position = position
    
    if (binding.arg === 'top') {
      el.style.top = binding.value
    } else if (binding.arg === 'bottom') {
      el.style.bottom = binding.value
    } else if (binding.arg === 'left') {
      el.style.left = binding.value
    } else if (binding.arg === 'right') {
      el.style.right = binding.value
    }
  }
}
</script>

6. 自定义指令的应用场景

  • 自动聚焦输入框
  • 添加滚动监听
  • 实现拖拽功能
  • 添加水印效果
  • 实现懒加载
  • 添加权限控制
  • 实现自定义表单验证

7. 自定义指令的最佳实践

  • 只在必要时使用自定义指令,优先考虑组件
  • 保持指令的职责单一
  • 使用清晰的命名,以 v- 前缀开头
  • 在 unmounted 钩子中清理资源,如事件监听器、定时器等
  • 对于复杂逻辑,考虑使用组合式函数封装

8. 自定义指令 vs 组件

特性 自定义指令 组件
用途 扩展 DOM 元素功能 构建 UI 组件
封装程度 轻量级,专注于 DOM 操作 完整的组件封装,包含模板、逻辑和样式
复用性 可以全局或局部复用 可以全局或局部复用
适用场景 DOM 操作、事件监听、样式修改 UI 组件、复杂交互