一、实验目标
-
掌握 Vue3 自定义指令的 4 个核心生命周期钩子(created/mounted/updated/unmounted)及执行时机
-
独立封装1个高频业务指令(v-permission 权限控制),实现一次封装、全局复用
-
熟练运用指令、动态参数、修饰符扩展指令功能,适配复杂业务场景
-
掌握自定义指令的 全局注册、局部注册 方式,理解 Tree-Shaking 兼容方案,避免打包冗余
-
解决实际开发中 "重复 DOM 操作、业务逻辑散落在组件" 的痛点,提升代码复用性和可维护性
二、实验步骤
1. 指令 :v-permission(权限控制)
需求:根据用户角色控制元素显示 / 隐藏(支持多个角色、反向权限)。
(1)封装实现(src/directives/permission.ts)
javascript
import type { Directive, DirectiveBinding } from 'vue'
// 模拟全局用户角色(实际从 Pinia/本地存储获取)
const userRoles = ['admin', 'editor']
const permissionDirective: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value: requiredRoles, modifiers } = binding
// 1. 若指令无值,直接隐藏
if (!requiredRoles || !Array.isArray(requiredRoles)) {
el.style.display = 'none'
return
}
// 2. 判断用户是否拥有所需角色(some:满足一个即可)
const hasPermission = requiredRoles.some(role => userRoles.includes(role))
// 3. modifiers.invert:反向权限(有角色则隐藏)
if (modifiers.invert ? hasPermission : !hasPermission) {
el.style.display = 'none'
}
}
}
export default permissionDirective
(2)全局注册
在 src/directives/index.ts 中统一导出指令:
javascript
import type { App } from 'vue'
import permission from './permission'
// 指令映射表(key:指令名,value:指令实现)
const directives = {
permission
}
// 全局注册所有指令
export const registerDirectives = (app: App) => {
Object.entries(directives).forEach(([name, directive]) => {
app.directive(name, directive)
})
}
(3)在 src/main.ts 中调用注册:
javascript
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { registerDirectives } from './directives'
// 1. 创建 Pinia 实例(TS 自动推断为 Pinia 类型)
const pinia = createPinia()
// 2. 安装持久化插件
pinia.use(piniaPluginPersistedstate)
// 3. 全局注册 Pinia(Vue 实例类型自动适配)
const app = createApp(App).use(pinia)
registerDirectives(app)
app.mount('#app')
(3) 实验结果


(4)实验说明
permission.ts中的模拟部分可以修改测试
bash
// 模拟全局用户角色(实际从 Pinia/本地存储获取)
const userRoles = ['admin', 'editor']
HelloWorld.vue中的逻辑也可以修改测试
bash
<template>
<!-- 只有 admin/editor 可见 -->
<button v-permission="['admin', 'editor']">编辑按钮</button>
<!-- 反向权限:admin 不可见 -->
<button v-permission.invert="['admin']">普通用户按钮</button>
</template>
三、核心原理
(一)自定义指令的本质与工作流程
Vue3 自定义指令的核心是 "封装 DOM 操作逻辑",本质是一个包含生命周期钩子的对象。其工作流程如下:
-
注册阶段 :通过
app.directive(name, directive)或局部注册,Vue 会将指令与指令名关联; -
解析阶段 :模板编译时,Vue 识别
v-xxx指令,将其与对应的 DOM 元素绑定; -
执行阶段 :DOM 元素触发对应生命周期(如挂载、更新、卸载)时,Vue 调用指令的对应钩子函数,传入
el(DOM 元素)和binding(参数对象); -
销毁阶段 :元素卸载时,调用
unmounted钩子,清理事件监听、定时器等,避免内存泄漏。
(二)关键 API 与参数解析
- 核心参数
DirectiveBinding
指令钩子的第二个参数 binding 包含指令的所有配置信息,类型定义如下:
javascript
interface DirectiveBinding<T = any> {
value: T; // 指令值(如 v-debounce="handleClick" 中的 handleClick)
oldValue: T | null; // 上一次的指令值(仅 updated 钩子可用)
arg?: string; // 动态参数(如 v-debounce:1000 中的 1000)
modifiers: Record<string, boolean>; // 修饰符(如 v-permission.invert 中的 { invert: true })
instance: ComponentPublicInstance | null; // 指令所在的组件实例
dir: Directive; // 指令本身的定义对象
}
- 生命周期钩子的设计逻辑
-
created:用于初始化准备(如存储初始状态),此时 DOM 尚未挂载,无法操作el的样式 / 事件; -
mounted:核心钩子,DOM 已挂载,可安全执行 DOM 操作(如绑定事件、设置样式); -
updated:组件更新时触发,用于同步指令状态(如指令值变化后更新 DOM); -
unmounted:用于资源清理(如移除事件监听、销毁定时器、停止观察者),避免内存泄漏。