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

相关推荐
anew___7 分钟前
《从零手写操作系统 (19):Ext2文件系统实战——从内存到磁盘的跨越》
java·服务器·前端·数据库·算法
吴声子夜歌30 分钟前
Nginx应用与运维——Nginx Web服务应用实战(HTTPS安全服务器的搭建)
运维·前端·nginx
liangshanbo121535 分钟前
Monorepo 工程化面试题:如何设计 Web + Node.js 中间层 + Shared + Scripts 的 Monorepo?
前端·node.js
IT_陈寒38 分钟前
Redis集群切换主节点时,服务竟然全员掉线
前端·人工智能·后端
wangbing112543 分钟前
开发指南147-WebSocket-前端
前端·websocket·网络协议
吴声子夜歌44 分钟前
Nginx应用与运维——Nginx Web服务应用实战(伪流媒体服务器的搭建)
运维·前端·nginx
ss2731 小时前
AI全栈实战 | 2.4-01 Node 与前端工程化:前端为什么离不开 Node,它不只是构建工具的宿主
前端
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_transit接口指南
前端·php
鬓戈1 小时前
Volta:解决 nvm「不能同时存在多个 Node 版本」的痛点
前端
吴声子夜歌1 小时前
Nginx应用与运维——Nginx Web服务应用实战(HTTP增强协议服务器的搭建)
运维·前端·nginx