Vue 2 与 Vue 3 自定义指令(Directive)详解
Vue 的自定义指令(Directive)允许开发者直接操作 DOM,实现原生 HTML 无法直接实现的功能(如自动聚焦、滚动加载等)。以下是 Vue 2 和 Vue 3 中自定义指令的核心差异及详细用法。
文章目录
一、基本用法对比
1. Vue 2 中注册指令
在 Vue 2 中,指令的定义和使用是基于 Vue.directive()
进行全局注册的。它主要通过 钩子函数 来与 DOM 交互,例如 bind
、inserted
、update
、componentUpdated
和 unbind
。
- 全局注册:通过
Vue.directive()
全局注册指令,所有组件可用。 - 局部注册:通过组件的
directives
选项局部注册。
// 全局注册自定义指令
Vue.directive('my-directive', {
// 指令绑定到元素时调用
bind(el, binding, vnode) {
console.log('指令绑定', el, binding, vnode);
},
// 指令插入到 DOM 中时调用
inserted(el) {
console.log('指令插入', el);
},
// 元素更新时调用
update(el, binding) {
console.log('元素更新', el, binding);
},
// 组件更新时调用
componentUpdated(el) {
console.log('组件更新', el);
},
// 指令解绑时调用
unbind(el) {
console.log('指令解绑', el);
}
});
// 全局注册:v-focus
Vue.directive('focus', {
inserted(el) {
el.focus();
}
});
// 局部注册
export default {
directives: {
focus: {
inserted(el) {
el.focus();
}
}
}
};
2. Vue 3 中注册指令
- 全局注册:通过应用实例的
directive()
方法注册,作用域限制在应用实例内。 - 局部注册:与 Vue 2 类似,通过组件的
directives
选项。
// 全局注册
const app = createApp(App);
app.directive('focus', {
mounted(el) {
el.focus();
}
});
// 局部注册
export default {
directives: {
focus: {
mounted(el) {
el.focus();
}
}
}
};
二、指令生命周期钩子对比
Vue 2 指令钩子
钩子函数 | 触发时机 | 参数 |
---|---|---|
bind | 指令第一次绑定到元素时 | el, binding, vnode |
inserted | 元素插入父节点时 | el, binding, vnode |
update | 组件更新时(可能发生在子组件更新前) | el, binding, vnode, oldVnode |
componentUpdated | 组件及子组件更新后 | el, binding, vnode, oldVnode |
unbind | 指令与元素解绑时 | el, binding, vnode |
Vue 3 指令钩子
钩子函数 | 触发时机 | 参数 |
---|---|---|
beforeMount | 元素挂载到 DOM 前(类似 bind ) | el, binding, vnode |
mounted | 元素挂载到 DOM 后(类似 inserted ) | el, binding, vnode |
beforeUpdate | 组件更新前(类似 update ) | el, binding, vnode |
updated | 组件更新后 | el, binding, vnode |
beforeUnmount | 组件卸载前(类似 unbind ) | el, binding, vnode |
unmounted | 组件卸载后 | el, binding, vnode |
三、核心差异详解
1. 钩子函数名称变化
- Vue 2 的
bind
→ Vue 3 的beforeMount
- Vue 2 的
inserted
→ Vue 3 的mounted
- Vue 2 的
unbind
→ Vue 3 的beforeUnmount
2. 参数对象差异
-
Vue 2 的
binding
对象:{ name: '指令名(不带 v-)', value: '指令绑定的值', oldValue: '旧值(仅在 update 和 componentUpdated 中可用)', expression: '绑定值的字符串形式(如 "user.name")', arg: '指令参数(如 v-my-directive:arg 中的 arg)', modifiers: '修饰符对象(如 v-my-directive.modifier 中的 { modifier: true })' }
-
Vue 3 的
binding
对象:{ instance: '当前组件实例', // 新增属性 value: '指令绑定的值', oldValue: '旧值(在 beforeUpdate 和 updated 中可用)', arg: '指令参数', modifiers: '修饰符对象', dir: '指令配置对象' // 新增属性(包含所有钩子函数) }
- 移除了
expression
和rawName
,新增instance
和dir
。
- 移除了
3. 碎片(Fragment)支持
- Vue 2:组件只能有一个根元素,指令绑定在根元素上。
- Vue 3:组件支持多个根元素(Fragment),指令需明确绑定到具体元素。
四、实战示例
场景:实现一个文本高亮指令 v-highlight
Vue 2 实现
// 全局注册
Vue.directive('highlight', {
bind(el, binding) {
el.style.backgroundColor = binding.value || 'yellow';
},
update(el, binding) {
el.style.backgroundColor = binding.value || 'yellow';
}
});
// 使用
<template>
<div v-highlight="'#ff0000'">高亮文本</div>
</template>
Vue 3 实现
// 全局注册
const app = createApp(App);
app.directive('highlight', {
beforeMount(el, binding) {
el.style.backgroundColor = binding.value || 'yellow';
},
updated(el, binding) {
el.style.backgroundColor = binding.value || 'yellow';
}
});
// 使用(支持多根元素)
<template>
<div v-highlight="'#ff0000'">高亮文本</div>
<p>其他内容</p>
</template>
五、高级用法
1. 动态指令参数
<!-- Vue 2 和 Vue 3 通用 -->
<template>
<div v-mydir:[dynamicArg].modifier="value"></div>
</template>
v-mydir 是自定义指令。
[dynamicArg] 表示动态参数,指令的行为会根据这个动态参数的值而改变。
.modifier 是修饰符,用来改变指令的特定行为。
value 是绑定的值,用来传递指令的具体数据。
<template>
<div>
<!-- 动态设置背景颜色 -->
<div v-color:[styleProp].animated="colorValue">
动态背景色和动画
</div>
<!-- 动态设置文本颜色 -->
<div v-color:[styleProp]="textColor">
动态文本颜色
</div>
</div>
</template>
<script>
export default {
data() {
return {
styleProp: 'backgroundColor', // 动态参数,控制样式属性
colorValue: 'lightblue', // 要绑定的背景颜色值
textColor: 'red' // 要绑定的文本颜色值
};
},
directives: {
// 定义一个自定义指令 `v-color`
color: {
// 动态参数([styleProp])和修饰符(.animated)
mounted(el, binding) {
const styleProp = binding.arg; // 获取动态参数,控制样式属性
const value = binding.value; // 获取绑定的值
const isAnimated = binding.modifiers.animated; // 获取是否启用动画的修饰符
// 设置样式属性
el.style[styleProp] = value;
// 如果启用动画效果,添加过渡动画
if (isAnimated) {
el.style.transition = 'all 1s ease-in-out';
}
}
}
}
};
</script>
<style>
div {
margin: 20px;
padding: 20px;
text-align: center;
}
</style>
2. 指令与 Composition API 结合(Vue 3)
import { ref, onMounted } from 'vue';
export default {
setup() {
const color = ref('red');
return { color };
}
};
// 自定义指令
app.directive('color', {
mounted(el, binding) {
el.style.color = binding.value;
},
updated(el, binding) {
el.style.color = binding.value;
}
});
六、迁移注意事项
- 钩子函数重命名:将
bind
改为beforeMount
,inserted
改为mounted
,unbind
改为beforeUnmount
。 - 访问组件实例:Vue 3 中通过
binding.instance
访问,替代 Vue 2 的vnode.context
。 - 碎片支持:确保指令绑定到具体的 DOM 元素,避免多根组件中的歧义。
七、总结对比表
特性 | Vue 2 | Vue 3 |
---|---|---|
注册方式 | Vue.directive() (全局) | app.directive() (应用实例作用域) |
钩子函数 | bind , inserted , unbind | beforeMount , mounted , beforeUnmount |
参数对象 | 包含 expression | 新增 instance 和 dir |
碎片支持 | 不支持(单根组件) | 支持(多根组件) |
TypeScript 支持 | 弱 | 强(完整类型定义) |
生命周期逻辑拆分 | 较简单 | 更细粒度(新增 updated 、beforeUpdate ) |
类型支持 | 需手动声明类型 | 天然支持 TypeScript |
通过对比可以看出,Vue 3 的指令系统更加模块化和灵活,同时解决了全局污染问题。如需进一步探讨具体场景的指令实现,可以随时提出!