Vue 自定义指令详解

Vue 自定义指令详解

Vue 的自定义指令允许开发者注册可复用的指令,直接作用于 DOM 元素。它们是操作底层 DOM 的理想选择,适用于需要直接与元素交互的场景,如聚焦输入框、添加事件监听器、实现懒加载等。


一、核心概念

1.内置指令和自定义指令

  • 内置指令 : v-model, v-show, v-if, v-for 等。
  • 自定义指令 : 开发者根据需求创建的以 v- 开头的指令,用于封装特定的 DOM 操作逻辑。

2. 作用和原理

  • 作用:封装DOM操作/事件逻辑,复用交互行为(如聚焦、拖拽),避免组件内直接操作DOM。
  • 原理:通过指令名(如v-focus)绑定到元素,响应生命周期钩子或值变化。

二、注册方式

1. 全局注册 (app.directive)

在应用实例上注册,所有组件均可使用。

javascript 复制代码
import { createApp } from 'vue'

const app = createApp({})

// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
  // 指令的钩子函数
  mounted(el) {
    el.focus() // 聚焦元素
  }
})

2. 局部注册 (directives 选项)

在组件内部注册,仅当前组件可用。

vue 复制代码
<template>
  <input v-focus />
</template>

<script>
export default {
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}
</script>

三、指令钩子函数 (生命周期)

1.触发时机

自定义指令提供了多个钩子函数,在不同阶段执行:

钩子 触发时机 参数
created 指令绑定到元素后立即调用(仅 Vue 3 )。在 beforeMount 之前。 el, binding, vnode, prevVnode
beforeMount 元素挂载前调用(Vue 2 中相当于 bind el, binding, vnode
mounted 元素挂载后调用(最常用 el, binding, vnode
beforeUpdate 元素更新前调用(仅 Vue 3 el, binding, vnode, prevVnode
updated 元素更新后调用(Vue 2 中相当于 componentUpdated el, binding, vnode, prevVnode
beforeUnmount 元素卸载前调用(仅 Vue 3 ,相当于 Vue 2 的 unbind el, binding, vnode
unmounted 元素卸载后调用(清理工作在此进行 el, binding, vnode

注意 : Vue 2 使用 bind, inserted, update, componentUpdated, unbind。Vue 3 统一为上述名称。

2.常用钩子说明

  • mounted: 最常用的钩子。元素已插入父 DOM,可以安全地进行 DOM 操作(如聚焦、初始化第三方库)。
  • unmounted : 必须在此处清理资源!如移除事件监听器、清除定时器、销毁第三方实例,防止内存泄漏。

四、钩子函数参数

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

  1. el : 指令绑定的元素。可以直接操作 DOM。
  2. binding : 一个对象,包含以下属性:
    • value: 传递给指令的值。v-my-directive="1 + 1",则 binding.value2
    • oldValue: 更新前的值(仅 beforeUpdateupdated 钩子中可用)。
    • arg: 指令的参数。v-my-directive:arg,则 binding.arg"arg"
    • modifiers: 包含修饰符的对象。v-my-directive.mod1.mod2,则 binding.modifiers{ mod1: true, mod2: true }
    • instance: 使用该指令的组件实例。
    • dir: 指令的定义对象。
  3. vnode: 绑定元素对应的虚拟节点。
  4. prevVnode : 上一个虚拟节点(仅 beforeUpdateupdated 中可用)。

五、功能强大的示例

1. 带参数和修饰符的焦点指令

vue 复制代码
app.directive('focus', {
  mounted(el, binding) {
    const { value, arg, modifiers } = binding
    
    if (value) { // 只有值为真时才聚焦
      el.focus()
      
      // 参数: 指定延迟时间
      if (arg) {
        const delay = parseInt(arg)
        setTimeout(() =&gt; el.focus(), delay)
      }
      
      // 修饰符: select 表示聚焦后全选文本
      if (modifiers.select) {
        el.select()
      }
    }
  },
  unmounted(el) {
    // 清理(如果有必要)
  }
})
vue 复制代码
<input v-focus:500.select="shouldFocus" />
<!-- 
  shouldFocus 为 true 时:
  - 延迟 500ms 后聚焦
  - 聚焦后自动全选文本内容
-->

2. 图片懒加载指令

vue 复制代码
app.directive('lazy', {
  mounted(el, binding) {
    // 创建 IntersectionObserver 实例
    const observer = new IntersectionObserver((entries) =&gt; {
      entries.forEach(entry =&gt; {
        if (entry.isIntersecting) {
          // 元素进入视口,加载真实图片
          el.src = binding.value
          // 加载完成后停止观察
          observer.unobserve(el)
        }
      })
    })
    
    // 开始观察元素
    observer.observe(el)
    
    // 将 observer 存储在元素上,便于后续清理
    el._lazyObserver = observer
  },
  unmounted(el) {
    // 卸载时必须断开观察,防止内存泄漏!
    if (el._lazyObserver) {
      el._lazyObserver.disconnect()
      delete el._lazyObserver
    }
  }
})
vue 复制代码
<img v-lazy="imageSrc" alt="Lazy Image" />

3. 权限控制指令 (简化版)

vue 复制代码
// 假设有一个全局的权限检查函数
function hasPermission(permission) {
  // 返回用户是否有该权限
  return userPermissions.includes(permission)
}

app.directive('permission', {
  mounted(el, binding) {
    const requiredPerm = binding.value
    if (!hasPermission(requiredPerm)) {
      // 移除没有权限的元素
      el.parentNode &amp;&amp; el.parentNode.removeChild(el)
      // 或者隐藏: el.style.display = 'none'
    }
  }
})
vue 复制代码
<button v-permission="'delete_user'">删除用户</button>
<!-- 如果用户无 'delete_user' 权限,按钮将被移除 -->

六、函数简写

如果 mountedupdated 钩子逻辑相同,可以简写为一个函数。

vue 复制代码
app.directive('color', (el, binding) =&gt; {
  // 等同于在 mounted 和 updated 钩子中执行
  el.style.color = binding.value
})
vue 复制代码
<p v-color="'red'">这段文字是红色的</p>

七、对象字面量​

如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。别忘了,指令也可以接收任何合法的 JavaScript 表达式。

vue 复制代码
template
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
js 复制代码
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

八、使用建议

  1. 命名 : 使用有意义的、动词性的名字,如 v-focus, v-lazy, v-tooltip
  2. 清理资源 : 在 unmounted 钩子中务必清理所有副作用(事件监听器、定时器、观察器等)。
  3. 避免复杂逻辑: 指令应专注于 DOM 操作。复杂的业务逻辑应放在组件或 Composition API 中。
  4. 类型安全 (TypeScript): 可以通过模块扩展为自定义指令提供类型提示。
vue 复制代码
// augment.d.ts
import { Directive } from 'vue'

declare module 'vue' {
  export interface Directives {
    focus: Directive
    lazy: Directive
  }
}

九、总结

Vue 自定义指令是操作原生 DOM 的强大工具。

  • 何时使用: 当需要直接、低级别的 DOM 操作,且该操作具有通用性时。
  • 核心 : 掌握钩子函数参数 (el, binding)。
  • 关键 : 在 unmounted 钩子中进行资源清理,防止内存泄漏。
  • 趋势: 在 Vue 3 Composition API 和 Teleport 等新特性下,部分传统指令场景(如模态框)有了更现代的解决方案,但指令在聚焦、懒加载、权限等场景依然不可替代。
相关推荐
90后的晨仔2 小时前
Vue 组件注册详解:全局注册 vs 局部注册
前端·vue.js
前端Hardy2 小时前
HTML&CSS:高颜值交互式日历,贴心记录每一天
前端·javascript·css
千码君20162 小时前
React Native:使用vite创建react项目并熟悉react语法
javascript·css·react native·react.js·html·vite·jsx
薄雾晚晴2 小时前
大屏开发实战:自定义原子样式,用 Less 混合自动生成间距类,告别重复样式代码
前端·css·vue.js
HYI2 小时前
vue3 作用域插槽下不能通过ref获取多个实例的坑
javascript·vue.js
进阶的鱼3 小时前
注意!使用props给子组件传参需要多想一步
前端·javascript·react.js
古夕3 小时前
微前端跨应用中通用前端业务模块的实现
前端·javascript·vue.js
AndyLaw3 小时前
<a>标签下载文件 download 属性无效?原来问题出在这里
前端·javascript
我是日安3 小时前
从零到一打造 Vue3 响应式系统 Day 19 - Reactive:reactive 的基础实现
前端·vue.js