vue2 和 vue3自定义指令有什么区别,都是怎么实现和使用一个指令

vue2 和 vue3自定义指令有什么区别,都是怎么实现和使用一个指令

Vue2 和 Vue3 自定义指令(Custom Directive) 整体思想一样:

直接操作 DOM 的一种扩展机制,通常用于权限控制、焦点、拖拽、懒加载等。

但 API 设计、生命周期、实现方式有明显变化。

4个层面:

1️⃣ Vue2 vs Vue3 指令生命周期区别 2️⃣ Vue2 按钮权限指令实现 3️⃣ Vue3 按钮权限指令实现 4️⃣ Vue3 指令底层设计变化

一、Vue2 vs Vue3 指令生命周期区别

Vue2 指令钩子

Vue2 指令有 5个生命周期

钩子 说明
bind 指令第一次绑定到元素
inserted 元素插入 DOM
update VNode 更新
componentUpdated 组件更新完成
unbind 解绑

示例

js 复制代码
Vue.directive('focus', {
  bind(el) {},
  inserted(el) {},
  update(el) {},
  componentUpdated(el) {},
  unbind(el) {}
})

Vue3 指令生命周期

Vue3 完全重写了指令生命周期,名字和组件生命周期保持一致。

Vue2 Vue3
bind beforeMount
inserted mounted
update updated
componentUpdated updated
unbind unmounted

示例

js 复制代码
app.directive('focus', {
  beforeMount(el) {},
  mounted(el) {},
  updated(el) {},
  unmounted(el) {}
})

二、Vue2 按钮权限指令实现

企业中最常见的自定义指令就是:

按钮权限控制

复制代码
v-permission

例如

html 复制代码
<button v-permission="'user:add'">新增</button>

如果没有权限:按钮直接删除

Vue2 指令实现

  1. 定义指令
js 复制代码
import store from '@/store'

Vue.directive('permission', {
  inserted(el, binding) {

    const { value } = binding
    const permissions = store.state.user.permissions

    if (value && value instanceof Array) {

      const hasPermission = permissions.some(
        p => value.includes(p)
      )

      if (!hasPermission) {
        el.parentNode.removeChild(el)
      }

    } else {
      throw new Error('权限指令需要数组')
    }
  }
})
  1. 使用指令
html 复制代码
<button v-permission="['user:add']">
新增用户
</button>

binding 参数结构

Vue2:

js 复制代码
binding = {
  name: 'permission',
  value: ['user:add'],
  oldValue: undefined,
  expression: "['user:add']",
  arg: undefined,
  modifiers: {}
}

三、Vue3 按钮权限指令实现

Vue3 写法更简洁。

  1. 创建指令

src/directives/permission.ts

ts 复制代码
import type { Directive } from 'vue'
import { useUserStore } from '@/store/user'

export const permission: Directive = {
  mounted(el, binding) {

    const { value } = binding
    const userStore = useUserStore()

    const permissions = userStore.permissions

    const hasPermission = permissions.some(
      p => value.includes(p)
    )

    if (!hasPermission) {
      el.parentNode?.removeChild(el)
    }
  }
}
  1. 注册指令

main.ts

ts 复制代码
import { createApp } from 'vue'
import { permission } from '@/directives/permission'

const app = createApp(App)

app.directive('permission', permission)

app.mount('#app')
  1. 使用指令
html 复制代码
<button v-permission="['user:add']">
新增用户
</button>

四、Vue3 指令底层设计变化

Vue3 指令其实是 VNode patch 阶段执行。

关键源码在:

bash 复制代码
runtime-core/directives.ts

核心函数:

复制代码
invokeDirectiveHook

简化源码:

ts 复制代码
export function invokeDirectiveHook(
  vnode,
  prevVNode,
  instance,
  name
) {
  const bindings = vnode.dirs

  for (let i = 0; i < bindings.length; i++) {
    const binding = bindings[i]

    const hook = binding.dir[name]

    if (hook) {
      hook(vnode.el, binding, vnode, prevVNode)
    }
  }
}

执行流程:

arduino 复制代码
template
   ↓
编译成 render
   ↓
VNode 上挂 dirs
   ↓
patch 阶段
   ↓
invokeDirectiveHook
   ↓
执行 mounted / updated

VNode结构:

js 复制代码
{
  type: 'button',
  props: {},
  dirs: [
    {
      dir: permission,
      value: ['user:add']
    }
  ]
}

五、Vue2 vs Vue3 指令实现差异

区别 Vue2 Vue3
注册 Vue.directive app.directive
生命周期 bind inserted update beforeMount mounted updated
调用时机 patch patch
binding 参数 复杂 更简单
类型支持 TS Directive 类型
底层实现 directive.js runtime-core/directives.ts

六、 Vue3 指令系统真正的运行路径

源码执行链路

scss 复制代码
template
 ↓
编译 render
 ↓
withDirectives()
 ↓
VNode.dirs
 ↓
patch()
 ↓
invokeDirectiveHook()
 ↓
执行 mounted / updated / unmounted

七、模板里的指令是怎么变成 VNode 的

模板

html 复制代码
<button v-permission="['user:add']">
新增
</button>

编译后的 render 函数(简化版)

js 复制代码
import { withDirectives, createVNode } from "vue"

return withDirectives(
  createVNode("button", null, "新增"),
  [
    [permission, ['user:add']]
  ]
)

关键函数:

scss 复制代码
withDirectives()

函数的作用是:把指令挂到 VNode 上

八、withDirectives 源码

源码位置:

bash 复制代码
packages/runtime-core/src/directives.ts

核心代码(简化)

ts 复制代码
export function withDirectives(vnode, directives) {

  const bindings = vnode.dirs || (vnode.dirs = [])

  for (let i = 0; i < directives.length; i++) {

    let [dir, value, arg, modifiers] = directives[i]

    bindings.push({
      dir,
      value,
      arg,
      modifiers
    })

  }

  return vnode
}

执行完之后:

VNode 结构会变成:

js 复制代码
{
  type: "button",
  props: null,
  children: "新增",
  dirs: [
    {
      dir: permission,
      value: ['user:add'],
      arg: undefined,
      modifiers: {}
    }
  ]
}

重点:VNode.dirs 这里存放所有指令

九、patch 阶段如何执行指令

Vue3 DOM 渲染核心:renderer.ts

当元素创建时:mountElement()

核心流程:mountElement(vnode, container)

简化代码:

ts 复制代码
const mountElement = (vnode, container) => {

  const el = vnode.el = document.createElement(vnode.type)

  // props
  patchProps(el)

  // children
  mountChildren()

  // 指令 mounted
  if (vnode.dirs) {
    invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
  }

}

所以:mounted 是在 DOM 创建完成之后执行

十、invokeDirectiveHook(核心函数)

源码:

bash 复制代码
runtime-core/directives.ts

核心代码:

ts 复制代码
export function invokeDirectiveHook(
  vnode,
  prevVNode,
  instance,
  name
) {

  const bindings = vnode.dirs

  for (let i = 0; i < bindings.length; i++) {

    const binding = bindings[i]

    const hook = binding.dir[name]

    if (hook) {
      hook(
        vnode.el,
        binding,
        vnode,
        prevVNode
      )
    }
  }
}

执行逻辑:

复制代码
遍历 vnode.dirs
   ↓
找到对应生命周期
   ↓
执行 mounted / updated

调用指令:

js 复制代码
permission.mounted(el, binding)

十一、binding 参数真正结构

当 VNode 更新:patchElement()

源码:

ts 复制代码
if (dirs) {
  invokeDirectiveHook(
    n2,
    n1,
    parentComponent,
    'updated'
  )
}

执行顺序:

复制代码
patch props
patch children
↓
directive updated

所以:updated 一定在 DOM 更新后执行

十二、指令 unmounted 执行时机

组件卸载:unmount()

源码:

ts 复制代码
if (vnode.dirs) {
  invokeDirectiveHook(vnode, null, instance, 'unmounted')
}

十三、Vue2 指令底层 vs Vue3 指令底层

Vue2 指令是在:patch.js 执行 updateDirectives()

源码:

js 复制代码
function updateDirectives(oldVnode, vnode) {

  const dirs = normalizeDirectives()

  for (key in dirs) {

    callHook(dir, 'bind')

  }

}

逻辑非常复杂

Vue3重写原因:

1️⃣ 生命周期混乱 2️⃣ diff逻辑复杂 3️⃣ 指令和组件生命周期不一致

Vue3改进:

bash 复制代码
统一生命周期
统一调用入口
VNode直接挂 dirs

后话:

为什么 Vue3 要有 withDirectives

Vue3 是 函数式 VNode 创建:

js 复制代码
h('button')

没有 template 的情况下:

less 复制代码
h('button', {}, '新增')

只能:

withDirectives()

js 复制代码
withDirectives(
  h('button'),
  [[permission, ['user:add']]]
)

所以:withDirectives 是 render 层 API

相关推荐
郭老二5 小时前
【前端】vue3:编译步骤
前端
不在逃避q6 小时前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁6 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One9856 小时前
高进度算法模板速记(待完善)
java·前端·算法
陈随易6 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师7 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户059540174468 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈8 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
kyriewen9 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒9 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端