Vue 自定义指令

在 Vue 中,组件负责"可见"的 UI,自定义指令则负责"不可见"的底层 DOM 行为。当你需要直接操作节点、监听第三方库、或封装浏览器原生 API 时,指令往往比组件更轻量、更灵活。Vue 为自定义指令设计了五条钩子函数,覆盖从首次绑定到最终解绑的完整生存周期。

一、bind:指令的"构造函数"

当指令第一次绑定到元素时触发,且只触发一次。

此时父节点可能尚未挂载到 DOM,因此不要在这里做需要获取父元素尺寸、位置的操作。

典型用途

  • 注册全局事件(如 window.resize)
  • 初始化第三方实例(如 new Sortable(el))
  • 设置一次性样式或属性
js 复制代码
bind(el, binding) {
  el.__onResize = () => { /* ... */ }
  window.addEventListener('resize', el.__onResize)
}

二、inserted:元素真正"落位"

当元素已经被插入父节点(哪怕父节点仍在内存中,尚未插入 document)时触发。

此时可以安全地读取 offsetParent、getBoundingClientRect 等信息。

典型用途

  • 启动需要 DOM 位置的动画
  • 实例化依赖父级布局的库(如 Popper.js)
  • 聚焦输入框
js 复制代码
inserted(el) {
  el.focus()
}

三、update:数据变了,视图还没 patch 完

每当组件的 VNode 重新渲染时触发,不管绑定值是否真的改变。

如果你只想在值变化时执行逻辑,需要手动比较 binding.value 与 binding.oldValue。

典型用途

  • 根据新值更新样式或文本
  • 节流/防抖高频 DOM 操作
js 复制代码
update(el, binding) {
  if (binding.value !== binding.oldValue) {
    el.style.color = binding.value
  }
}

四、componentUpdated:整个组件已经 patch 完成

与 update 不同,此钩子在当前组件及其所有子组件的 DOM 更新周期结束后触发。

适合需要等待子节点渲染完毕再执行的场景。

典型用途

  • 重新计算滚动容器高度
  • 在子列表渲染后刷新第三方滚动条
js 复制代码
componentUpdated(el) {
  el.scrollTop = el.scrollHeight
}

五、unbind:指令的"析构函数"

当指令与元素解绑(元素被 v-if 移除、路由切换、手动销毁)时触发,且只触发一次。

务必在此清理所有副作用,防止内存泄漏。

典型用途

  • 移除全局事件监听
  • 销毁第三方实例
  • 清除定时器
js 复制代码
unbind(el) {
  window.removeEventListener('resize', el.__onResize)
  el.__onResize = null
}

六、钩子参数速览

所有钩子都会收到统一的四个参数,按出场频率排序:

  1. el

    绑定到的真实 DOM 节点,可直接操作。

  2. binding

    一个对象,携带所有绑定信息:

    • name:指令名(不含 v-)
    • value:当前绑定值
    • oldValue:上一次绑定值(仅在 update 和 componentUpdated 中)
    • expression:字符串形式的表达式
    • arg:指令参数,如 v-color:red 中的 red
    • modifiers:修饰符对象,如 { prevent: true }
  3. vnode

    Vue 生成的虚拟节点,可访问 context、key 等元数据。

  4. oldVnode

    更新前的旧虚拟节点,仅在 update 和 componentUpdated 中可用。

实战示例:一个"可关闭提示"指令

js 复制代码
export default {
  bind(el, { value }) {
    el.style.position = 'relative'
    el.innerHTML += '<span class="close">×</span>'
    el.__close = el.querySelector('.close')
    el.__handler = () => el.remove()
    el.__close.addEventListener('click', el.__handler)
  },
  unbind(el) {
    el.__close.removeEventListener('click', el.__handler)
  }
}
相关推荐
曹牧10 分钟前
Eclipse 工作空间(Workspace)的默认 JDK 配置
前端
其实防守也摸鱼1 小时前
Cylab在线靶场——挑战图书馆初学者指南
服务器·前端·数据库·chrome·安全·网络安全·靶场
拆房老料3 小时前
【第四部分:Agentic 智能体设计模式】16. 一个 Prompt 搞不定复杂任务?试试 Prompt Chaining
java·前端·人工智能·设计模式·prompt
曹牧9 小时前
C#:空合并运算符
java·前端·数据库
小羊没烦恼!9 小时前
铁道部新客票系统设计(二)
开发语言·前端·数据库·python·oracle·c#·php
智能直播12 小时前
RK3518:1.5G 内存 + 8G 存储,如何跑起硬件编解码、Web、AI 识别、绿幕特效
前端·人工智能·5g
掘金酱13 小时前
【10.24 特别活动】AI 时代,程序员的新饭碗在哪里?
前端·人工智能·后端
excel13 小时前
多进程环境下使用 PM2 部署 Socket.IO:需要注意什么
前端
IT_陈寒14 小时前
Redis的原子性竟然失效了?记一次分布式锁翻车实录
前端·人工智能·后端
Interview Aid11215 小时前
Snowflake OA 面试真题:前缀和、BFS 与动态规划
面试·动态规划·宽度优先